Skip to content
Back to skills

Jira Issue Tracker

ASecurity

Operating mechanics for a project whose tracker is Jira — status as numbered transitions, cloudId and JQL quoting, issue links and their invertible direction, whole-field description writes, and what only a human can change. Load it when the project's docs/agents/issue-tracker.md names Jira. Read tracker-contract.md for what the pipeline requires of any tracker.

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 5, 2026
ai-agentsgoexpressgitapi

Works with

  • api
  • mcp

Security analysis

A100/100

Scanned October 5, 2026

npx -y skills add thientranhung/astragentic --skill jira-issue-tracker --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Jira Issue Tracker?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Jira Issue Tracker
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/thientranhung-jira-issue-tracker/badge)](https://www.skillsdirectory.com/skills/thientranhung-jira-issue-tracker)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: jira-issue-tracker
description: Operating mechanics for a project whose tracker is Jira — status as numbered transitions, cloudId and JQL quoting, issue links and their invertible direction, whole-field description writes, and what only a human can change. Load it when the project's docs/agents/issue-tracker.md names Jira. Read tracker-contract.md for what the pipeline requires of any tracker.
---

# Tracker adapter: Jira

**Measured on a live project that moved Linear → Jira on 2026-08-21**, team-managed. Every trap below cost that project a run or a correction. What the pipeline
requires of *any* tracker is `.agents/tracker-contract.md`; this file is the HOW for one of them.

**The project half stays in the project.** This adapter carries no site, no `cloudId`, no project
key, no transition-id table — the project's `docs/agents/issue-tracker.md` owns those, and they
are per-project by nature. Substitute as you read.

Reach it through the **Atlassian MCP tools**.

## Two coordinates a session cannot discover

**`cloudId` appears in no URL a human pastes.** It must be written down in the project half, or
every session starts by hunting for it (`getAccessibleAtlassianResources`).

**A project key can collide with a JQL keyword — always quote it.** A two-letter key that is
also a JQL word makes `project = <KEY>` fail to parse; `project = "<KEY>"` works. This bites once per session otherwise, and the error does not
say "quote your key".

## Status is a TRANSITION, taken by a number

This is the single biggest difference from every other tracker: you do not assign a status, you
take a transition, and transitions are identified by **numeric ids that are project-specific,
non-sequential and not guessable**. States added later take low ids.

**So a remembered transition id is a valid write to a state you did not mean.** It succeeds,
nothing errors, and no query flags it.

```
getTransitionsForJiraIssue   →   transitionJiraIssue with an id FROM THAT RESPONSE
```

**Read the transitions every time. The extra call IS the guard**, and it is cheaper than the
class of bug it prevents. A transition-id table in the project half is a convenience for humans
reading it, never the thing a session writes from.

**Adding or renaming a state is an OWNER action in the Jira UI, on both project types**, and the
MCP tools cannot touch a workflow at all — they create, edit, transition and link issues, nothing
more. A session that needs a state which does not exist is blocked on a human; say so rather than
improvising with labels.

*How the UI gets there differs, and only one half is measured.* On **company-managed**, the state
is added in Project settings → Workflows and is invisible to the API until the workflow is
**published** — that vocabulary is company-managed's, and this claim is inherited rather than
measured here. On **team-managed**, a status is added as a board column and there is no publish
step. **Re-measure before relying on either.**

**Team-managed vs company-managed changes what the board is.** On a **team-managed** (next-gen)
project the statuses **are** the board — there is no separate status→column mapping to keep in
sync, and requirement 5 is met by the product. On a **company-managed** project a status mapped
to no board column is invisible on the board, which is a second place for truth to live. Know
which one the project is before reasoning about its board.

**There is no need for a `Duplicate` state.** Jira carries duplication as a LINK, which is
strictly better than a status because it names *which* ticket absorbed this one: move the
absorbed ticket to the cancelled state, link it, comment on both.

## The claim protocol, which this adapter never carried

`thomas.md` writes the assignee as `builder/<ticket-id>`. **Jira's `assignee` takes an
accountId**, so it cannot hold that either — the same position GitHub and Linear are in, and
until now this file said nothing at all, so the protocol ran here with no adapter behind it.

- **The claim** is setting `assignee` to your own accountId (`lookupJiraAccountId` resolves it
  once; the project's `issue-tracker.md` carries it thereafter).
- **The frontier query** is JQL: `project = <KEY> AND assignee IS EMPTY AND statusCategory !=
  Done`, minus anything with an unfinished `is blocked by` link.
- **The Builder identity** goes in the dispatch record
  (`.astraler/state/dispatch-record.json`), since no field holds it.
- **The readback interlock is weakened; the git one is not.** A readback cannot tell whose
  claim it read, so `git worktree add -b <ticket-branch>` failing is the *only* thing that
  decides a same-second race. Losing it means leaving the assignee exactly as you found it.
- **Releasing a claim**: confirm the branch and worktree are gone, rather than reading the
  assignee back.

**So concurrency on one Jira account rests entirely on git**, exactly as on the other two.

## Dependencies are links, and the direction is silently invertible

| Relationship | Link type | How |
|---|---|---|
| A must finish before B | `Blocks` | `createIssueLink`, `inwardIssue` = A (the blocker), `outwardIssue` = B (the blocked) |
| A is absorbed by B | `Duplicate` | `inwardIssue` = A (the duplicate), `outwardIssue` = B (the survivor) |
| A and B merely touch | `Relates` | **prefer a sentence in the body over a link** |

**The inward/outward order is genuinely confusing and easy to invert, and a backwards `Blocks`
link corrupts the frontier query silently.** Create the link, then read the issue back and check
the **rendered wording** says what you meant — `X` must read *"is blocked by Y"*, not the
reverse. Reading back the payload you just sent proves nothing; read the rendering.

**`Relates` is noise at volume.** One migration measured **189 candidate `relatedTo` edges
against 8 real blocking edges** — carrying them all would have put nine entries in one ticket's
Linked issues panel while saying nothing about sequencing. A mention in the body renders as a
live key and carries the same information where it is readable.

**Jira's advantage, and the reason to pick it:** `Blocks` is expressible in **one JQL query**, so
the frontier is one call rather than the N+1 loop GitHub forces.

## `editJiraIssue` replaces `description` WHOLE

There is no partial update. Anything you do not send back is deleted. Two consequences, both
measured:

- **Never retype a description you meant to patch.** Read it, substitute, send the whole field
  back. An agent recomposing prose from memory silently drops words — it happened to a
  Vietnamese sentence and to a diacritic, and neither showed up in any error.
- **Build the JSON payload programmatically.** Hand-typed escape sequences corrupted `Ưu tiên`
  into `Ư u tiên` on the first attempt at exactly this task. This bites any project whose
  content is not ASCII.

## The readiness state exists for the human

Whatever the project calls it (`To Do`, `Ready`), the claimable-and-unclaimed state is
requirement 5 made visible, and three rules keep it honest:

1. **Write it when the ticket becomes UNBLOCKED, not when it is claimed.** Move it in the same
   action that closes its last blocker. Writing it and then moving to in-progress moments later
   is a state nobody could have read. A ticket already unblocked and shaped when filed is
   **created** in that state.
2. **Readiness plus an open `is blocked by` link is a contradiction.** When you add a blocking
   link to a ready ticket, move it back in the same breath.
3. **A blocker that is not an issue does not exist.** Create an entity for the real constraint —
   a decision, a credential, a deploy — and link at it. **A decision ticket is first-class and is
   NEVER dispatched to an agent**; it closes when the owner answers. Say so in its body.

## Two things that arrive with a Jira project

**Forms.** A ticket arriving with `form` / `form-*` labels and an empty description is a real
request from a real person, not noise. Triage it; never delete it.

**Migration provenance.** Where tickets came from another tracker, they carry a provenance label
and a first description line pointing back. **Do not strip either** — they are the only way back
to the original when a migrated ticket reads oddly. Expect attachments not to have survived a
Linear → Jira move: Linear's image URLs are short-lived signed links.

## What the project's `issue-tracker.md` must carry

Site URL · `cloudId` · project key, name, id, and **team-managed or company-managed** · board
URL · issue types · priorities · link types · the state table with its transition ids (as a
human's reference, not a session's source) · the ticket prefix · that project's own decisions.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.