AGENT SKILL

jira-genie

Search, create, edit, and manage Jira issues via jira-genie CLI. Use when the user needs to interact with Jira — find tickets, create tasks, update status, extract templates, or automate workflows. Includes recovery patterns for generic 400 errors, sprint discovery without board permissions, and unattended traceability workflows.

License 许可
MIT
最近更新
2026-09-18
Stars
★ 8
Forks
0

01 前言 Front Matter

name
jira-genie
description
Search, create, edit, and manage Jira issues via jira-genie CLI. Use when the user needs to interact with Jira — find tickets, create tasks, update status, extract templates, or automate workflows. Includes recovery patterns for generic 400 errors, sprint discovery without board permissions, and unattended traceability workflows.

03 SKILL.md 正文

Jira Genie

Operate Jira Cloud through the jira CLI. All commands output JSON to stdout.

Prerequisites

Check auth status before any operation:

jira auth status

If not logged in:

jira auth login

Check if schema is synced (needed for field resolution and completion):

jira fields list --filter summary

If empty or missing, sync:

jira fields sync                      # field list + discover projects
jira fields sync --project DEV        # also sync type schemas for a project

Agent fast path and recoveries

Read this before choosing commands for unattended workflows:

  • Search with jira search; do not invent jira issue list.

  • If a workflow only needs traceability, create the core issue first and add optional metadata afterward.

  • --json is the friendly path for normal use because it resolves names and converts Markdown descriptions. For high-reliability automation, or after any generic 400 with valid-looking --json, use a minimal --raw-payload create:

    jira issue create --raw-payload '{"fields": {"project": {"key": "DEV"}, "issuetype": {"name": "Task"}, "summary": "Real summary"}}'
    
  • Do not create probe/test issues to test schema. The first successful create should be the real ticket.

  • If jira issue edit --json or --set returns a generic 400 for a valid-looking field, retry that field with --raw-payload and the exact Jira API shape. Edit one optional field per command while recovering.

  • For active sprint discovery in automation, prefer JQL: jira search "project = DEV AND sprint in openSprints() ORDER BY updated DESC". Board APIs can return 403. Sprint assignment is best-effort.

  • Assignment normally uses jira issue assign <key> <email>; raw accountId edits are fallback.

  • Do not assume jira issue create --json can atomically set parent, description, assignee, links, and optional fields. For unattended reliability, create the core issue first, then enrich it one concern at a time.

  • For long or multi-line descriptions, write Markdown to a temp file and use jira issue edit <key> --body-file /tmp/description.md after creation. Avoid embedding large descriptions in create JSON during automation.

  • If jira issue link returns a JSONDecodeError, do not retry blindly. The link may have succeeded while the CLI failed parsing an empty response. Verify with jira issue get <key> --fields issuelinks first.

  • If a transition is not available, use the statuses printed in the error and walk through the required intermediate state.

  • Markdown bodies (descriptions, comments) are auto-converted to ADF — but do not nest inline code inside another inline mark (e.g. **\text`**). The current converter emits marks=[strong, code]` on a single text node, which Atlassian rejects with a generic HTTP 400. See "Known issues" below.

Searching Issues

Use JQL queries. Always returns a JSON array of formatted issues.

# Open issues in a project
jira search "project = DEV AND status != Done"

# Current user's work
jira search "assignee = currentUser() AND status != Done"

# Children of an epic
jira search "parent = DEV-100"

# Current sprint
jira search "project = DEV AND sprint in openSprints()"

# With specific fields
jira search "project = DEV" --fields summary,status,assignee,parent

The output is an array of {key, summary, status, assignee, priority, type} objects.

Pagination: Search returns up to 50 results. For larger result sets, narrow the JQL query (add date ranges, status filters, etc.) rather than expecting all results at once.

Reading Issues

# Formatted (clean JSON)
jira issue get DEV-123

# Specific fields only
jira issue get DEV-123 --fields summary,status,parent,components

# Raw API response (all fields, nested structure)
jira issue get DEV-123 --raw

Use --raw when you need fields not in the default format (description, comments, custom fields, sprint info).

Creating Issues

From JSON with friendly names

jira issue create --json '{
  "project": "DEV",
  "issuetype": "Task",
  "summary": "Implement feature X",
  "parent": "DEV-100",
  "priority": "P2: Medium",
  "components": ["API"],
  "labels": ["backend"]
}'

Field names are resolved automatically: story_points → customfield_10036, team → customfield_10001, etc.

Returns: {"id": "12345", "key": "DEV-125", "self": "https://..."}.

Setting descriptions

Descriptions accept Markdown — it's auto-converted to Atlassian Document Format (ADF):

jira issue create --json '{
  "project": "DEV",
  "issuetype": "Task",
  "summary": "Fix the auth flow",
  "description": "## Problem\n\nUsers are getting **401 errors** after token refresh.\n\n## Steps to reproduce\n\n1. Login normally\n2. Wait for token expiry\n3. Try any API call\n\n## Notes\n\n- Affects `PaymentService`\n- See [docs](https://example.com/auth)"
}'

Supported Markdown elements:

  • Headings (# through ######)
  • Bold (**text**), italic (*text*), inline code (`text`), strikethrough (~~text~~)
  • Links ([text](url))
  • Bullet lists (- item) and ordered lists (1. item), including nested
  • Code blocks (```language)
  • Blockquotes (> text)
  • Horizontal rules (---)

Plain text works too — it becomes a single paragraph.

Caveat (see Known issues): ADF rejects the code mark combined with any other inline mark. Avoid **\code`**, `code`, and `code`in Markdown bodies. Use a fenced code block, or split the formatting (e.g.See `code`).

For pre-built ADF (rare), pass a dict — it goes through untouched:

jira issue create --json '{
  "description": {"type": "doc", "version": 1, "content": [...]}
}'

The same applies to the environment field.

With a template

Templates provide defaults. Only specify what varies:

jira issue create --template backend --summary "Fix the race condition"

Before using a template with a parent, verify the parent is still the right active epic:

jira template show backend
jira issue get DEV-100 --fields summary,status,components

If the parent is closed or stale, search for the active epic before creating the ticket:

jira search "project = DEV AND issuetype = Epic AND component = 'SomeTeam' AND status != Closed ORDER BY updated DESC" --fields summary,status,components
jira search "project = DEV AND issuetype = Epic AND summary ~ 'SomeProject' ORDER BY updated DESC" --fields summary,status,components

Override template fields:

jira issue create --template backend --json '{"parent": "DEV-200"}' --summary "Different epic"

Composition order (later overrides earlier):

template → --json → --set/--summary → resolved → API

With --set flags

jira issue create --json '{"project": "DEV", "issuetype": "Task"}' \
  --summary "Quick task" \
  --set parent=DEV-100 \
  --set priority="P1: High"

With --body-file (long description)

When the description is too large to fit comfortably on the command line, write it to a Markdown file first:

jira issue create --json '{"project": "DEV", "issuetype": "Task"}' \
  --summary "Fix the bug" \
  --body-file /tmp/description.md

The file contents are loaded into the description field and Markdown-to-ADF conversion runs as usual.

Reliable minimal create / raw payload

--json is preferred for normal interactive use. Use --raw-payload when a workflow must not be blocked by field resolution, when templates contain fields you need to omit, or when jira issue create --json returns a generic 400 despite valid-looking fields.

Create the core issue with only required fields first:

jira issue create --raw-payload '{"fields": {"project": {"key": "DEV"}, "issuetype": {"name": "Task"}, "summary": "Create the ticket"}}'

Prefer stable ids if friendly keys/names fail:

jira issue get DEV-123 --raw | jq '.fields.project, .fields.issuetype'
jira issue create --raw-payload '{
  "fields": {
    "project": {"id": "<project_id>"},
    "issuetype": {"id": "<issuetype_id>"},
    "summary": "Create the ticket"
  }
}'

After raw creation, fill description and optional fields with jira issue edit. This keeps core ticket creation unblocked even when one template field or resolved field is bad.

Reliable create/enrich/link sequence

For unattended creation with any optional metadata, use separate steps. This avoids losing the whole create to a generic 400 from one optional field and makes recovery deterministic.

# 1. If copying context from an existing issue, inspect it first.
jira issue get DEV-123 --fields summary,status,assignee,parent,issuetype

# 2. Create the real issue with only stable required/core fields.
jira issue create --raw-payload '{"fields":{"project":{"key":"DEV"},"issuetype":{"name":"Task"},"parent":{"key":"DEV-EPIC"},"summary":"Issue summary"}}'

# 3. Add long descriptions separately from a temp Markdown file.
cat > /tmp/dev-new-description.md <<'EOF'
Description in Markdown.
EOF
jira issue edit DEV-NEW --body-file /tmp/dev-new-description.md

# 4. Assign explicitly if needed.
jira user me
jira issue assign DEV-NEW user@example.com

# 5. Link separately if needed. If this reports JSONDecodeError, verify before retrying.
jira issue link --type Relates DEV-NEW DEV-123
jira issue get DEV-NEW --fields issuelinks

Use this sequence for follow-ups, traceability tickets, and any create where parent/description/assignment/links/optional fields are involved. The first successful create should always be the real issue, not a probe.

Extracting Templates from Existing Tickets

When the user wants a template based on how they already create tickets:

Step 1: Find representative tickets

jira search "reporter = currentUser() AND type = Task ORDER BY created DESC" --fields summary,parent,components,labels,priority

Step 2: Inspect fields and schema

# Look at a real ticket's fields
jira issue get DEV-123 --raw

# Check all available fields for that type (required fields, allowed values)
jira fields schema --project DEV --type Task

Look at the fields that are consistent across tickets (project, issuetype, parent, components, labels) — these become the template. Ignore fields that vary per ticket (summary, description).

Step 3: Create the template

Extract the common fields into a template:

jira template create my-template --json '{
  "project": "DEV",
  "issuetype": "Task",
  "parent": "DEV-100",
  "components": ["API"]
}'

Step 4: Verify

jira template show my-template

Managing Templates

jira template list                    # all templates
jira template show backend            # view one
jira template create NAME --json '{}'  # create
jira template delete NAME             # remove

jira template default backend         # set as default
jira template default                 # show current default
jira template default --clear         # remove default

When a default is set, jira issue create --summary "..." uses it automatically.

There is no reliable non-interactive JSON edit flow for templates. To replace a stale template, create a new one, set it as default, verify, then delete the old one:

jira template create someproject-new --json '{"project":"DEV","issuetype":"Task","parent":"DEV-100","components":["SomeTeam"]}'
jira template default someproject-new
jira template show someproject-new
jira template delete someproject-old

Editing Issues

# Set individual fields
jira issue edit DEV-123 --set priority="P1: High" --set story_points=5

# JSON override; useful for Markdown description because it converts to ADF
jira issue edit DEV-123 --json '{"description": "## Updated analysis\n\nThe root cause is..."}'
jira issue edit DEV-123 --json '{"team": "Backend", "labels": ["urgent"]}'

# Set description directly (accepts Markdown — same converter, same caveat about nested code marks)
jira issue edit DEV-123 --description "## Updated\n\nNew description"

# Read description from file (avoids shell-escaping pain for long Markdown)
jira issue edit DEV-123 --body-file /tmp/description.md

# Raw payload; bypasses field/value resolution and sends exact Jira API JSON
jira issue edit DEV-123 --raw-payload '{"fields": {"summary": "Updated title"}}'

If --set or --json returns a generic 400 even though the field/value looks valid, retry with --raw-payload. Apply one optional field per request while recovering so one bad field does not block the rest:

jira issue edit DEV-123 --raw-payload '{"fields": {"components": [{"name": "SomeTeam"}]}}'
jira issue edit DEV-123 --raw-payload '{"fields": {"customfield_10008": [<sprint_id>]}}'
jira issue edit DEV-123 --raw-payload '{"fields": {"<customfield_id>": "<value>"}}'

Use jira fields list --filter <keyword> or jira fields schema --project DEV --type Task to find custom field ids and allowed values.

Workflow Operations

# Transition status
jira issue transition DEV-123 "In Progress"
jira issue transition DEV-123 "Done"

# Assign
jira issue assign DEV-123 alice@example.com

# Comment
jira issue comment DEV-123 "Fixed in commit abc1234"

# Link issues (--type values: "blocks", "is blocked by", "relates to", "duplicates", "is duplicated by", "clones", "is cloned by")
jira issue link DEV-123 DEV-456 --type blocks

If the target status is not currently available, the CLI error lists available transitions. Walk through the required intermediate statuses, for example:

jira issue transition DEV-123 "In Progress"
jira issue transition DEV-123 "In Review"

Bulk Operations

Only bulk edit is supported. Use it to update the same field(s) across multiple issues:

jira bulk edit DEV-1 DEV-2 DEV-3 --set parent=DEV-100
jira bulk edit DEV-1 DEV-2 --set priority="P1: High"
jira bulk edit DEV-1 DEV-2 --json '{"team": "Backend"}'

For bulk creation or transitions, loop over individual commands.

Schema Inspection

Use these to discover what fields and values are available:

# All fields with their IDs and types
jira fields list

# Search for a field by name
jira fields list --filter story

# Full schema for a project + issue type (required fields, allowed values)
jira fields schema --project DEV --type Task

The schema output tells you exactly what to pass when creating or editing issues.

Sprints and Boards

For active sprint discovery in automation, prefer JQL first because board APIs may return 403:

jira search "project = DEV AND sprint in openSprints() ORDER BY updated DESC" --fields key,summary,status,assignee,sprint

If you need the raw sprint id, inspect an issue already in the active sprint:

jira issue get DEV-123 --raw | jq '.fields.customfield_10008'

Sprint assignment is best-effort. If setting the sprint field returns 400, do not block a traceability workflow whose required end state is ticket, assignee, status, and PR linkage. If sprint assignment is still desired, use raw payload with the exact sprint custom field shape for the project:

jira issue edit DEV-123 --raw-payload '{"fields": {"customfield_10008": [<sprint_id>]}}'

Use board APIs only when you specifically need board/backlog operations and permissions allow them:

jira board list --project DEV
jira sprint current --board 42
jira sprint list --board 42 --state active,future
jira sprint issues 123 --fields summary,status,assignee
jira board backlog 42

Users

jira user me                # current user
jira user search "alice"    # find users

Error Handling

All errors output structured JSON to stderr:

{"error": "404 Client Error: Not Found for url: ...", "type": "HTTPError"}

Parse the error and type fields to decide how to recover. When a command fails, read the error message and recover:

  • Field validation error on create/edit → check required fields and allowed values:
    jira fields schema --project DEV --type Task
    
  • Generic 400 on create/edit with valid-looking --json or --set → retry with --raw-payload using exact Jira API field shapes. For creates, create the minimal core issue first, then edit optional fields afterward. For edits, set one optional field per request while recovering.
  • Generic 400 when posting a comment / description from Markdown → check whether the Markdown contains nested inline marks like **\code`**, `code`, or `code`. The converter emits invalid ADF for those and Atlassian rejects the document. Workaround: use a fenced code block, or split the formatting (See `code` here`).
  • Template create fails or creates tickets under old work → inspect the template with jira template show, verify its parent issue status, and replace the template if the parent is stale.
  • Unknown field name → list available fields and find the correct name:
    jira fields list --filter <keyword>
    
  • Invalid transition → the issue's current status may not allow that transition. Use the available statuses printed in the error and transition through the appropriate intermediate state.
  • Permission denied / 403 → the authenticated user lacks permission for that project or operation. Verify with jira auth status and jira user me. If board/sprint APIs are forbidden, use JQL search fallbacks.
  • Invalid JQL → simplify the query, check field names and function syntax.

Field Resolution Reference

When using --json or --set, field names and values are resolved automatically:

You write API receives
"project": "DEV" "project": {"key": "DEV"}
"issuetype": "Task" "issuetype": {"name": "Task"}
"parent": "DEV-100" "parent": {"key": "DEV-100"}
"priority": "P1: High" "priority": {"name": "P1: High"}
"team": "Backend" "customfield_10001": {"value": "Backend"}
"components": ["API"] "components": [{"name": "API"}]
"story_points": 5 "customfield_10036": 5
"description": "**Markdown** text" parsed as Markdown, converted to ADF
"description": {ADF doc} passed through as-is (dicts are never wrapped)

Use --raw-payload to bypass all resolution and send exact JSON to the API.

Known issues

Markdown → ADF: nested inline code marks produce invalid ADF (HTTP 400)

When Markdown contains an inline code span nested inside another inline mark, the converter emits a single text node with two marks (e.g. marks=[strong, code]). ADF's schema does not allow the code mark to combine with any other inline mark, so Atlassian rejects the document with a generic 400 Bad Request.

Triggers (all produce HTTP 400 from the comment / description endpoints):

  • **\foo.py`**` (bold around inline code)
  • *\foo.py`*` (italic around inline code)
  • [\foo.py`](url)` (link around inline code)
  • Any sentence-level bold that contains inline code as a child: **See \foo.py` here**`

Safe alternatives:

  • Plain inline code: `foo.py` — fine, even with quotes/braces/parens inside (`os.system(f"echo {x}")` is OK).
  • Fenced code block — fine for any language and any content.
  • Bold of plain text: **foo.py** — fine.
  • Split the formatting at the boundary: **See** \foo.py` here`.

Quick local repro (run with the jira-genie tool's interpreter):

from jira_genie.adf import markdown_to_adf
adf = markdown_to_adf("Bad: **`foo.py`**")
# Walk to a text node and you'll see marks=[strong, code] — invalid per ADF schema.

Common Agent Patterns

Gather context before working

jira issue get DEV-123
jira search "parent = DEV-123" --fields summary,status

Update a ticket's description

Use jira issue edit with --description (short Markdown), --body-file (long Markdown), or --json (Markdown inside a JSON payload):

# Short description inline
jira issue edit DEV-123 --description "## Updated analysis\n\nThe root cause is..."

# Long description from file (avoids shell-escaping pain)
jira issue edit DEV-123 --body-file /tmp/description.md

# Or via --json (when you want to set description alongside other fields)
jira issue edit DEV-123 --json '{"description": "## Updated analysis\n\nThe root cause is..."}'

For long descriptions assembled in shell, write to a file first, then use one of the above:

cat > /tmp/desc.md << 'EOF'
## Full analysis

Detailed description here...
EOF
jira issue edit DEV-123 --body-file /tmp/desc.md

Add a comment

# Short comment
jira issue comment DEV-123 "Fixed in commit abc1234"

# Long comment from file
jira issue comment DEV-123 --body-file /tmp/analysis.md

Comments accept Markdown — it's auto-converted to Atlassian Document Format. Same caveat as descriptions: avoid nested inline code marks (see Known issues).

Create task, do work, close

jira issue create --template backend --summary "Fix the bug"
# ... do the work ...
jira issue transition DEV-124 "In Progress"
jira issue comment DEV-124 "Working on it"
# ... finish ...
jira issue transition DEV-124 "Done"
jira issue comment DEV-124 "Fixed in commit abc1234"

Discover and report

jira search "project = DEV AND sprint in openSprints() AND status = 'To Do'" --fields summary,assignee,priority

04 同仓库更多 Skills