| name | jira-atlassian-cli |
| description | Load before any Jira interaction or mcp__atlassian__* call. Holds required
field values (team ID, sprint IDs, custom field mappings) unavailable
elsewhere; skipping causes missing fields on created tickets. Triggers: Jira
URLs, ticket keys (e.g. ABC-123), MCP tools mcp__atlassian__*, acli, or
keywords create/read/edit/search/transition, ticket, Jira, issue, sprint,
backlog, refinement, work item.
|
Jira Tools
Team Context (load before any Jira call)
Private team values live in ~/OneDrive/work/contexts/jira-data.yaml.
- Read the ENTIRE file once per session. No partial reads; risks missing
my_team (pointer to default team key) or mixing cross-team values.
- Resolve team: user-named team, else top-level
my_team field.
- Substitute placeholders from resolved team:
{cloud_id}, {project_key},
{team_field}, {team_id}, {sprint_field}, {refinement_sprint_id},
{active_sprint_board_id}.
- File missing: ask user. Do not guess. End turn.
Active (current) sprint is NOT constant
-
{refinement_sprint_id} is stable; the active sprint id rotates every sprint.
-
Never hardcode the active sprint id. Resolve it at runtime.
-
{active_sprint_board_id} is the constant agile board; sprints are its
children. Resolve the active sprint from the board in one call (verified):
acli jira board list-sprints --id {active_sprint_board_id} --state active --json
Read id from the single active sprint. Flag is --id (the board id), not
--board. The command is board list-sprints; there is no
acli jira sprint list.
-
Do NOT use the old search+view path (customfield_10020 is rejected in a
search --fields list and stripped from search results). The board endpoint
replaces it.
Tool Priority
Prefer MCP (mcp__atlassian__*) for everything. Supports markdown
(contentFormat: "markdown") and full ADF (contentFormat: "adf"). Pass
cloudId: "{cloud_id}" on all MCP calls.
Use acli only when MCP cannot do the job, or not available (bulk ops, advanced
JQL export, edge cases).
MCP Create/Edit Notes
-
contentFormat: "adf" required when description contains acceptance criteria
or any checklist. Jira markdown renders - [ ] as literal text in bullets,
not checkboxes. Only taskList/taskItem ADF nodes produce real checkboxes.
-
contentFormat: "markdown" for simple descriptions (headings, bold, lists,
links, no checklists).
-
contentFormat: "adf" for full control (checkboxes, panels, complex layouts).
Pass ADF JSON as the description string.
-
Always set team and sprint via additional_fields:
"additional_fields": {
"{team_field}": "{team_id}",
"{sprint_field}": {refinement_sprint_id}
}
-
Epic link: "parent": "{project_key}-NNN".
-
API response body is unreliable for verifying rendering. Create/edit/get
responses echo description as markdown even when sent or requested as ADF
(including responseContentFormat: "adf"); ADF features (checkboxes, expand,
panels) appear flat in the echo but render correctly in Jira. Verify by
opening the issue URL.
-
expand IS valid in issue ADF. editJiraIssue INVALID_INPUT almost always
means a structural error in a sibling node, not expand. Most common:
paragraph-wrapped taskItem.content (see ADF Reference).
ADF Reference (for contentFormat: "adf")
Pass ADF JSON as the description value.
Canonical docs:
https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/.
Per-node spec at .../document/nodes/<nodeName>/.
Debugging a node
-
Fetch the node spec for allowed content, attrs, marks:
defuddle parse \
"https://developer.atlassian.com/cloud/jira/platform/apis/document/nodes/<nodeName>/" \
--md
-
Still rejected: isolate by replacing the suspect subtree with a minimal valid
node (e.g. taskList -> bulletList). Success means the suspect is the
cause. MCP errors usually name the offending node.
-
MCP-stricter-than-spec traps:
taskItem.content: bare inline nodes, NOT paragraph-wrapped. Same rule
inside expand. See
atlassian/atlassian-mcp-server#25
(also confirms markdown - [ ] does NOT auto-convert to taskList; send
ADF).
tableCell / tableHeader / panel / expand: block-node children only;
wrap raw text in paragraph.
ADF Node Types
Use inside description.content:
-
Paragraph:
{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }
-
Heading:
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "..." }] }
-
Code block:
{ "type": "codeBlock", "attrs": { "language": "tsx" }, "content": [{ "type": "text", "text": "..." }] }
-
Bullet list:
{ "type": "bulletList", "content": [{ "type": "listItem", "content": [{ "type": "paragraph", "content": [...] }] }] }
-
Ordered list:
{ "type": "orderedList", "content": [/* same as bulletList */] }
-
Bold text:
{ "type": "text", "text": "...", "marks": [{ "type": "strong" }] }
-
Inline code:
{ "type": "text", "text": "...", "marks": [{ "type": "code" }] }
-
Task list (checkboxes):
{ "type": "taskList", "attrs": { "localId": "ac-1" }, "content": [{ "type": "taskItem", "attrs": { "localId": "ac-1-1", "state": "TODO" }, "content": [{ "type": "text", "text": "..." }] }] }
Acceptance criteria MUST use taskList/taskItem, not bulletList.
taskItem.content MUST be bare text nodes; paragraph-wrapping is rejected
with INVALID_INPUT (see Debugging trap above).
-
Expand (collapsible section):
{ "type": "expand", "attrs": { "title": "..." }, "content": [<block nodes>] }
Children: block nodes (paragraph, bulletList, heading, etc.). Use to
hide long context (findings, traces) below the fold.
-
Inline smart link (renders as Jira card / Notion preview / any URL):
{ "type": "inlineCard", "attrs": { "url": "https://{cloud_id}/browse/{project_key}-NNN" } }
Use inside a paragraph content array. Works for any URL.
-
Panel (info/warning/error/success callout):
{
"type": "panel",
"attrs": { "panelType": "info" },
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Note text here" }]
}
]
}
panelType: info, note, warning, error, success.
-
Table:
{
"type": "table",
"attrs": { "isNumberColumnEnabled": false, "layout": "default" },
"content": [
{
"type": "tableRow",
"content": [
{
"type": "tableHeader",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Header" }]
}
]
}
]
},
{
"type": "tableRow",
"content": [
{
"type": "tableCell",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Cell" }]
}
]
}
]
}
]
}
Header row: tableHeader. Data rows: tableCell. Cell content: block nodes
(wrap text in paragraph).
-
Horizontal rule: { "type": "rule" }
-
Link mark:
{ "type": "text", "text": "click here", "marks": [{ "type": "link", "attrs": { "href": "https://..." } }] }
Issue Linking (Blocks, Relates, etc.)
Use MCP mcp__atlassian__createIssueLink. Available link types:
| Name | Inward (passive) | Outward (active) |
|---|
| Blocks | is blocked by | blocks |
| Relates | relates to | relates to |
| Duplicate | is duplicated by | duplicates |
| Fixes | is fixed by | fixes |
| Improves | is improved by | improves |
Directionality for "Blocks": inwardIssue = blocker, outwardIssue = blocked.
A blocks B:
inwardIssue: "{project_key}-A", outwardIssue: "{project_key}-B", type: "Blocks"
Subtasks
MCP create with issueTypeName: "Sub-task" and parent: "{project_key}-NNN".
Same parent field as epic linking; issue type determines subtask vs. epic
child.
Sprint Field
{sprint_field} takes a plain integer, not an object:
"{sprint_field}": {refinement_sprint_id}
NOT { "id": ... } (errors with "Number value expected as the Sprint id").
Active (current) sprint vs refinement
jira-data.yaml holds refinement_sprint_id and active_sprint_board_id. The
current/active sprint id is NOT in the yaml and changes every sprint. To target
the active sprint (user says "current sprint", "this sprint", "not refinement"),
resolve it live from the board in one call:
acli jira board list-sprints --id {active_sprint_board_id} --state active --json
then read id from the single active sprint. Use that integer as
{sprint_field} in the create JSON.
Parent / Epic Linking
MCP create: "parent": "{project_key}-NNN". MCP edit
(mcp__atlassian__editJiraIssue):
fields: {"parent": {"key": "{project_key}-NNN"}}.
acli Fallback Reference
Use acli jira workitem subcommands only when MCP cannot do the job.
Quick Reference
Substitute {project_key} before running.
| Operation | Command |
|---|
| View | acli jira workitem view {project_key}-N |
| View (all) | acli jira workitem view {project_key}-N --fields '*all' --json |
| Search | acli jira workitem search --jql "project = {project_key} AND ..." --limit 20 |
| Create | acli jira workitem create --project {project_key} --type Task --summary "..." |
| Create ADF | acli jira workitem create --from-json file.json |
| Edit | acli jira workitem edit --key {project_key}-N --summary "New title" |
| Transition | acli jira workitem transition --key {project_key}-N --status "In Progress" |
| Comment | acli jira workitem comment create --key {project_key}-N --body "..." |
acli pitfalls (verified)
acli jira workitem edit prompts y/N; pass --yes for non-interactive runs.
- No
acli jira me. Get your accountId via JQL:
acli jira workitem search --jql "assignee = currentUser()" --limit 1 --json,
read fields.assignee.accountId.
- Assignee on create: nest inside
additionalAttributes as
"assignee": { "accountId": "..." }.
- Active sprint id: resolve at runtime (see "Active (current) sprint" above);
never reuse
{refinement_sprint_id} for it.
acli JSON Template (for --from-json)
Assignee on create: add assignee.accountId inside additionalAttributes.
There is NO acli jira me. Get your own accountId via:
acli jira workitem search --jql "assignee = currentUser()" --limit 1 --json
then read fields.assignee.accountId.
{
"additionalAttributes": {
"{team_field}": "{team_id}",
"{sprint_field}": {refinement_sprint_id},
"assignee": { "accountId": "712020:..." }
},
"parentIssueId": "{project_key}-NNN",
"projectKey": "{project_key}",
"summary": "Ticket title",
"type": "Task",
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Description here." }]
}
]
}
}
acli Edit JSON Format
Uses "issues" (array of keys) instead of "projectKey":
{
"issues": ["{project_key}-N"],
"description": {
"type": "doc",
"version": 1,
"content": [...]
}
}
Ticket framing (summary = deliverable, not motivation)
The summary and description must describe the WORK to be done, not the event
that prompted it. A PR, bug sighting, or incident is the MOTIVATION; it belongs
in the description as context, never as the headline.
- Wrong: "Remove duplicate handler type in chat-embedded (PR #2100)" — that
frames a one-off PR fix when the actual deliverable was a review-rule
enhancement.
- Right: "Enhance AI-review duplication rule: same-app scope + near-dup
detection" — names the durable change; the PR is one motivating line in the
body.
Before creating: ask "what is the lasting deliverable?" If the summary names a
PR/ticket/incident as its subject, rewrite it to name the change instead.
Common Workflows
Read a ticket from a URL
Extract key from URL (e.g. {project_key}-NNN from
https://{cloud_id}/browse/{project_key}-NNN), then MCP
mcp__atlassian__getJiraIssue with issueIdOrKey: "{project_key}-NNN".
Search team tickets
MCP mcp__atlassian__searchJiraIssuesUsingJql, or fallback:
acli jira workitem search --jql "project = {project_key} AND status = 'New'" --limit 20
acli jira workitem search --jql "project = {project_key} AND sprint = {refinement_sprint_id}" --limit 50