jira-cli · diff
git:20261019.ddb50dd to git:20261019.bc7946b
10 added, 1 removed. Audit A to A.
---
name: jira-cli
allowed-tools: Bash, Read, Write, Edit, Grep, Glob, WebFetch
description: |-
Drive Atlassian Jira from the terminal with the `jira` CLI (jira-cli, v1.7.0) against ANY Jira — Cloud or on-premise/Data Center. Covers the full command surface (issue / epic / sprint / board / project / release), the non-interactive automation contract (`--no-input` + `--plain`/`--raw`/`--csv` for agent-safe, parseable output), JQL filtering, GitHub/Jira markdown → Atlassian Document Format (ADF) conversion, authentication for every backend (Cloud API token, on-prem basic, PAT/bearer, mTLS), and live-discovery of instance-specific values (project keys, issue types, statuses, priorities, link types, custom fields) instead of guessing them.
when_to_use: |-
Trigger whenever the user wants to create, read, search, edit, transition, assign, comment on, link, clone, or report on Jira issues / epics / sprints / boards / releases FROM THE COMMAND LINE or a script — even when the tool is not named. Fires on "jira-cli", "jira issue list/create/edit/move", "jira sprint", "jira epic", "jira me/init", "transition a ticket from the CLI", "bulk-update Jira", "script Jira", "JQL from the terminal", "export Jira to CSV/JSON", "create a Jira ticket non-interactively", "automate Jira in CI", "the jira command hangs / prompts", "jira 401", "JIRA_API_TOKEN", "JIRA_AUTH_TYPE=bearer", "PAT or mTLS for Jira", "Jira markdown / ADF not rendering" — against Cloud OR Server/Data Center. NOT for the Jira REST API directly, the Jira web UI, or Confluence.
---
# jira-cli — Atlassian Jira from the terminal
- Drive any Jira (Cloud, Server, Data Center) non-interactively. Assume nothing about the target's project keys, workflows or field schemes — discover them, then act.
+ Target audience: an operator or agent driving Jira non-interactively — creating and transitioning tickets, running JQL, exporting issues, and scripting bulk changes — against any Jira deployment (Cloud, Server, or Data Center). This skill is **instance-agnostic**: it never assumes the target's project keys, workflows, or field schemes — it shows how to *discover* them and then act safely.
`jira` is [ankitpokhrel/jira-cli](https://github.com/ankitpokhrel/jira-cli), a single static Go binary inspired by GitHub's `gh`. It is **not** an official Atlassian tool.
## Three rules
1. **Writes:** `--no-input` + every required flag + `</dev/null`. **Reads:** `--plain`, `--raw` or `--csv`. Without these, `create`/`edit`/`assign`/`move`/`comment add`/`worklog add` wait on a prompt forever, and `list`/`view`/`epic list`/`sprint list` print a TUI instead of data.
2. **Never guess instance values.** Issue types, statuses, priorities, resolutions, link types, components and custom fields are per-project and case-sensitive (`-tBug` fails where the type is `Defect`). Discover them first (Step 0).
3. **Bodies are converted, lossily** — GFM → ADF on Cloud, → wiki markup on Server/DC. Pitfall 8 and `references/markdown-adf.md`.
## Version & source of truth
- **Pinned at v1.7.0** (released 2025-08-31, the current latest). Verify locally: `jira version`.
- **`--help` is the authoritative flag reference.** `jira <cmd> <subcmd> --help` prints flags, arguments, aliases, and examples. If this skill disagrees with `--help` on a flag's name or arguments, trust `--help` (and update the skill); where this skill flags a help text as wrong (`--component`), trust the skill. Generate full man pages with `jira man --generate --output <dir>`.
+ - This skill's exhaustive flag/argument tables live in `references/commands.md`, captured from the v1.7.0 binary.
## Cloud vs Server / Data Center — know which backend you're on
The CLI talks to two different Jira APIs and the behavior diverges in ways that change real commands. Check with `jira serverinfo` (`Deployment Type: Cloud` vs `Server`). The command *surface* is identical; these semantics are not:
| Aspect | Jira **Cloud** | Jira **Server / Data Center** |
|---|---|---|
| REST API | v3 | v2 |
| Description/comment format | GFM/Jira markdown → **ADF** (auto-converted) | **Jira wiki markup** — `create`/`comment` convert GFM→wiki, but `edit` sends it **verbatim** (#935); prefer `h2.`, `*bold*`, `{code}` |
| Auth | email + **API token** | **password** (basic), or **PAT** (`JIRA_AUTH_TYPE=bearer`), or **mTLS** |
| User identity for `-a`/`-r` | **accountId** (GDPR strict mode, #342) — email/display name may not resolve | **username** (or display name) |
| `--paginate <from>:` offset | **ignored** — can't page past the first 100 (#898) | **works** — old search API still honors `startAt` |
| SSO in front of the instance | rare | API must be reachable directly with a **PAT**; basic-auth/email hits the SSO HTML login → `401` / `invalid character '<'` (#477, #822) |
| Releases/Versions, sprints | feature-gated | same, plus older Agile API quirks |
| Non-English instance | — | `epic`/`issue create` can fail on translated type names: hand-fill `epic.name`, `epic.link`, `issue.types.*.handle` in the config (`references/config-auth.md`) |
When a recipe below assumes Cloud (ADF markdown, accountId, the pagination cap), the Server/DC equivalent is in the right-hand column. Auth/SSO specifics: `references/config-auth.md`. Markdown specifics: `references/markdown-adf.md`.
## Step 0 — ALWAYS discover the instance before acting
A skill that hardcodes project keys or status names is wrong on the next Jira. Before any create/edit/move/assign, learn what the target instance actually offers. These are read-only and safe:
```bash
jira me # confirm auth + identity (prints the account/email)
jira project list # KEY NAME TYPE LEAD — tab-separated table, takes no output flags
jira board list # ID NAME TYPE — board IDs for sprints (also takes no output flags)
# Discover the field VALUES a project accepts — read them off existing issues:
jira issue list -p PROJ --plain --no-truncate --paginate 5 # see real types/statuses/priorities in use
jira issue view PROJ-123 --raw | jq '.fields | {type:.issuetype.name, status:.status.name, priority:.priority.name, resolution:.resolution.name}'
jira issue view PROJ-123 --raw | jq '.fields | keys' # custom field IDs (customfield_XXXXX)
```
For transitions specifically, the **valid target states depend on the issue's current status and the project workflow** — there is no global list. List the valid targets non-interactively by asking for a state that cannot exist: `jira issue move KEY "__list__" </dev/null` exits 1 with `Available states for issue KEY: 'To Do', 'In Progress', …`. Script the exact string from that list. Full discovery recipes: `references/config-auth.md`.
## Command map
| Goal | Command | Notes |
|---|---|---|
| Who am I / is auth working | `jira me`, `jira serverinfo` | `$(jira me)` is the self-reference idiom |
| List/search issues | `jira issue list` (aliases `ls`, `search`) | Filters + JQL; see `references/jql-and-filters.md` |
| View one issue | `jira issue view KEY` | `--comments N`, `--raw` for JSON |
| Create issue | `jira issue create -t<Type> -s"..." --no-input` | `-P` parent (epic link / required for sub-task) |
| Edit issue | `jira issue edit KEY ... --no-input` | `--label`/`--component`/`--fix-version` **append**; `-value` removes |
| Transition | `jira issue move KEY "State"` (aliases `transition`, `mv`) | `--comment`/`-a`/`-R` inline; state is workflow-defined |
| Assign | `jira issue assign KEY <user|$(jira me)|default|x>` | `x` = unassign; user must be exact email/display name |
| Comment | `jira issue comment add KEY "body"` | `--internal` for service-desk-internal; markdown→ADF |
| Worklog | `jira issue worklog add KEY "2d 1h 30m" --no-input` | `--started`, `--timezone`, `--new-estimate` |
| Link / unlink | `jira issue link IN OUT <Type>` / `unlink` / `link remote` | `<Type>` is instance-defined (`Blocks`, `Duplicates`, …) |
| Clone | `jira issue clone KEY -H"find:replace"` | copy + tweak fields; a `:` inside find/replace skips the replacement but still clones |
| Delete (permanent) | `jira issue delete KEY [--cascade]` | irreversible; `--cascade` also deletes subtasks |
| Epics | `jira epic list [KEY]` / `create -n"Name"` / `add` / `remove` | `create` needs `-n/--name`; `add`/`remove` ≤50 at once, and need admin on next-gen — else `issue edit KEY -P EPIC` |
| Sprints | `jira sprint list [ID]` / `add` / `close` | `--current`/`--prev`/`--next`/`--state`; get IDs from `--table` |
| Releases (versions) | `jira release list [-p PROJ]` | requires Releases/Versions enabled on the instance |
| Open in browser | `jira open [KEY]` | `--no-browser` prints the URL instead |
| Projects / boards | `jira project list`, `jira board list` | discovery |
Full flag tables, arguments, and aliases for every command: **`references/commands.md`**.
## The automation contract (read this before scripting)
Output flags (reads) — bare `list`/`view` open an interactive UI, so any piping needs one of:
- `--plain` (+ `--no-headers`, `--no-truncate`, `--columns key,summary,status`, `--delimiter "|"`) — tabular text; column names come from `--help`.
- `--raw` — Jira REST JSON (parse with `jq`; shape `.[].fields.*`).
- `--csv` — CSV with headers.
- `--paginate <limit>` — cap result count (max 100). **Jira Cloud, v1.7.0: the `<from>:` offset is silently ignored** — Atlassian's new JQL search API dropped `startAt`, so there is **no way to page past the first 100 issues** (#898). Narrow with JQL/filters instead. Server/Data Center (older API) still honors `<from>:<limit>`.
Write flags:
- `--no-input` — **the load-bearing flag.** Disables prompting for non-required fields. Pair with every required flag so the command runs unattended.
- `--web` — open the result in a browser after the write (skip in headless/CI).
Idioms:
```bash
# Self-reference
ME=$(jira me)
# Create → capture key → act on it
KEY=$(jira issue create -tTask -s"Automated task" --no-input --raw </dev/null | jq -r '.key')
jira issue assign "$KEY" "$ME"
jira issue move "$KEY" "In Progress"
# Bulk: freeze keys, loop, record failures
jira issue list -q'assignee = currentUser() AND status = "To Do"' --plain --columns key --no-headers </dev/null > keys.txt
: > failed.txt
while read -r k; do
jira issue move "$k" "In Progress" --comment "Picking up" </dev/null || echo "$k" >> failed.txt
done < keys.txt
```
A bulk run is done when every key in `keys.txt` was attempted and `failed.txt` is empty — re-run the loop over `failed.txt` after fixing the cause. On Cloud, `keys.txt` holding exactly 100 lines means the query was truncated (#898): split the JQL.
More patterns (CSV/JSON pipelines, dashboards, safe bulk edits): **`references/scripting.md`**.
## Critical pitfalls
1. **Forgetting `--no-input` on a write hangs the process.** In a non-interactive context (CI, agent, `&&` chain) this looks like the command "froze". Every `create`/`edit`/`assign`/`move`/`comment add`/`worklog add` in a script needs `--no-input` plus all required positional/flag values. **Known bug:** even with `--no-input`, the body-reading writes (`create`, `edit`, `comment add`, `epic create`) can still block on stdin when it's a socket/subprocess pipe — jira-cli treats "stdin is not a TTY" as "read the body from stdin" and waits for EOF (#948/#984). Append `</dev/null` when shelling out from an agent.
2. **Guessing field values.** `-tBug`, `-sDone`, `-yHigh`, `link ... Blocks` all reference *instance-defined, case-sensitive* strings. Run Step-0 discovery first. A failed write with "specify a valid issue type" / "field cannot be set" almost always means the value doesn't exist on that project.
3. **`edit` list flags append, never replace.** `--label`, `--component` and `--fix-version` add to the existing set. `--help` says `--component` "Replace components" — wrong on v1.7.0, it appends too. To swap a value, remove it with a minus prefix in the same call: `jira issue edit KEY --label -stale --label fresh --component -OldComp --component NewComp --no-input`.
4. **`-b/--body` and the positional comment body beat `--template`.** If both are passed, the flag/positional wins and the template is silently ignored. Use one or the other.
5. **Epic creation quirks.** `jira epic create -n"Epic name" -s"Summary" [-b"body"] --no-input` works non-interactively on **both** classic and next-gen. Two gotchas: **`-n/--name` is required even on next-gen** (where its value is then ignored — the mandatory check still demands it), and `epic create` has **no `--raw`** flag (it prints `Epic created\n<url>`). To capture the new key as JSON, use `jira issue create -tEpic -s"…" --no-input --raw` instead (Epic is an issue type), then attach children with `-P/--parent EPIC-KEY` (the flag is "parent" because next-gen reuses the parent relationship). The `? Epic Key` prompt comes from `epic add` when its `EPIC-KEY` arg is missing — not from create.
6. **Sub-tasks require `-P/--parent`,** and the parent must be a type that allows sub-tasks. "Given parent work item does not belong to appropriate hierarchy" means `-P` points at something (e.g. an epic, or another sub-task) that can't hold sub-tasks.
7. **`delete` is irreversible and `--cascade` deletes every subtask of each match.** For bulk cleanup:
- **Never write a delete loop fed by live `issue list` output** — not even when asked for a paste-and-go one-liner. Never "re-query until empty" around the 100-row cap for a delete.
- **Recommend the reversible option as the default, first in the answer:** bulk `move` to the workflow's closed state with `-R<resolution>` (find the exact strings first), or add a label — it can be undone; deletion cannot. Offer deletion second.
- **If deletion stays:** freeze keys to a file (`--plain --no-headers --columns key > keys.txt`), have the user review that file and pin the JQL's status names to real ones, then delete only from the file — after the user confirms. Cloud returns ≤100 per query (#898), so build the file from narrower JQL windows, de-duplicated.
8. **Markdown → ADF is lossy.** Prefer GitHub fenced code blocks (```` ``` ````) over Jira `{code}` (which can leak escape characters). `~~strike~~` renders as `-text-`; `@user` mentions need Jira's `[~accountid]` form; emoji shortcodes (`:rocket:`) and raw HTML are dropped. For anything structured, use `--template file.md` and test on one issue first. Details: `references/markdown-adf.md`.
9. **Assignee/watcher must match exactly.** Pass an exact email or display name. On many Jira Cloud instances, GDPR strict mode means assignment resolves by accountId — if `assign KEY "Jane Doe"` fails, try the email, or look up the accountId via `jira issue view ... --raw`. `x` unassigns; `default` uses the project's default assignee.
10. **`-q/--jql` runs *within the configured project's context*.** To query across all projects, add a project clause yourself: `-q'project IS NOT EMPTY'` or name projects in the JQL. Plain filter flags (`-s`, `-y`, `-l`, …) and a `-q` JQL can combine. **The word `project` anywhere in the JQL — even inside a text search like `summary ~ "project kickoff"` — drops the automatic project scope and searches every project (#1020).** When the JQL contains that word, add `project = PROJ` explicitly.
11. **Auth is via the `JIRA_API_TOKEN` environment variable, not the config file.** The token never lives in `.config.yml`. Cloud wants an **API token** (not the account password); on-prem basic wants the **password**; PAT wants the token **plus** `JIRA_AUTH_TYPE=bearer`. A 401 is nearly always a missing/wrong `JIRA_API_TOKEN` or the wrong auth type. See `references/config-auth.md`.
## What to read next
| File | Read when… |
|---|---|
| `references/commands.md` | Looking up exact flags, arguments, aliases for any command. Full v1.7.0 surface. |
| `references/jql-and-filters.md` | Building a `list`/`epic list`/`sprint list` query — filter flags, the date syntax (`week`, `-7d`, `2025-09-15`), `~` negation, `x` unassigned, JQL examples. |
| `references/markdown-adf.md` | Writing a description/comment with formatting — GFM vs Jira markup, ADF conversion limits, templates, here-docs, `$'...'` newlines. |
| `references/scripting.md` | Automating — non-interactive recipes, `--raw`+`jq` and `--csv` pipelines, safe bulk edits, capturing created keys, dashboards. |
| `references/config-auth.md` | First-time setup, multi-instance configs, every auth type (Cloud/basic/PAT/mTLS), env vars, and the full instance-discovery recipes. |
| `references/troubleshooting.md` | A specific error or symptom — hangs, 401s, "valid issue type", parent-hierarchy errors, empty output, pager weirdness. |
| `references/known-issues.md` | Tracking an upstream bug the skill works around — `(#NNN)` tags in the body map to this table (status, what it affects). |
| `references/sources.md` | Verifying or freshening external claims; the `Freshened:` stamp dates the last full check. |
## Quick recipes
```bash
+ # Smoke test: am I connected and what can I see?
+ jira me && jira project list
+
# List my open issues, parseable
jira issue list -q'assignee = currentUser() AND statusCategory != Done' \
--plain --no-headers --columns key,status,summary
# Create a bug, non-interactively, and print its key
jira issue create -tBug -s"Login 500 on submit" -yHigh -lregression \
-b$'## Steps\n1. ...\n\n## Expected\n...' --no-input --raw </dev/null | jq -r '.key'
# Transition with a comment and resolution
jira issue move PROJ-42 "Done" -RFixed --comment "Shipped in 1.2.3"
# Export to CSV — at most 100 rows per query on Cloud (#898); for more, run narrower JQL windows and concatenate
jira issue list -p PROJ --csv --paginate 100 > issues.csv
+
+ # Add a sub-task under a story
+ jira issue create -t"Sub-task" -P PROJ-100 -s"Write tests" --no-input </dev/null
```
+
+ For anything beyond these, drill into the `references/` files — they carry the exhaustive flag tables, JQL grammar, ADF rules, and auth matrix.