thientranhung

jira-issue-tracker

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.

thientranhung 4 Updated 1mo ago
GitHub

Install

npx skillscat add thientranhung/astragentic/jira-issue-tracker

Install via the SkillsCat registry.

SKILL.md

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.