# Jira

> Manage Jira issues from the command line. Use when working with Jira issues, creating tasks, updating or transitioning status (including closing issues with a resolution), assigning work, linking issues, commenting (including JSM internal/public notes), logging work/worklogs/time spent, managing versions, setting or reading custom fields (Story Points, Sprint, Severity, etc.), searching for issues, bulk-editing/transitioning/deleting issues by JQL, moving issues between projects, reparenting issues under an epic, watching or voting on issues, or working with Jira Software Agile — boards, sprints, backlog ranking.

- Skill: `avantmedialtd/jira` (Agent Skill)
- Install (CLI): `npx skillmds@latest add avantmedialtd/jira`
- Raw SKILL.md: https://api.skillmd.com/api/skills/avantmedialtd/jira/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: avantmedialtd (https://skillmd.com/u/avantmedialtd)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/avantmedialtd/jira

---


# Jira CLI

Command-line tool for managing Jira issues via `af jira`.

## Setup

Add the following environment variables to your project's `.env` file (preferred names first; legacy `JIRA_*` names are still accepted and fall back to these when unset):

- `ATLASSIAN_BASE_URL` — Your Atlassian instance URL (e.g., `https://company.atlassian.net`)
- `ATLASSIAN_EMAIL` — Your Atlassian account email
- `ATLASSIAN_API_TOKEN` — API token from https://id.atlassian.com/manage-profile/security/api-tokens

## Project Defaults

If the project instructions specify a default Jira project key (e.g., `Jira project: PROJ`), use it as the default for `--project`, `list`, `types`, and JQL queries. Explicit user input always overrides the default.

## Quick Reference

Run bare `af jira` (no subcommand) for the full command and flag reference. Note `af jira --help` is intercepted by af's router and prints only a short stub.

### Read Operations

- `af jira get <issue-key>` — Get issue details (renders a "Custom Fields" section when any are set)
- `af jira list <project> [--limit N] [--show-field a,b,c]` — List project issues; `--show-field` adds custom-field columns
- `af jira search "<jql>" [--show-field a,b,c]` — Search with JQL
- `af jira projects` — List projects
- `af jira types <project>` — List issue types
- `af jira fields [--project <key> --type <name>] [--refresh] [--verbose]` — List custom fields; with `--project`/`--type` marks required fields and shows allowed values (create-scoped)
- `af jira editmeta <issue-key>` — Editable fields and allowed values for an **existing** issue (the edit-scoped twin of `fields`) — run this before `af jira update --field ...`
- `af jira transitions <issue-key>` — List available transitions, expanded with screen metadata: which transitions present a screen and what fields they require (this output tells you which `--resolution`/`--field` args the `transition` call needs)
- `af jira comment <issue-key>` — List comments
- `af jira remote-link <issue-key>` — List remote links
- `af jira versions <project>` — List all versions in a project
- `af jira version <version-id>` — Get version details

### Write Operations

- `af jira create --project <key> --type <type> --summary "<text>" [--description "<text>"] [--priority <name>] [--labels a,b,c] [--parent <key>] [--estimate <time>] [--fix-version <v1,v2>] [--affected-version <v>] [--field name=value]... [--field-json '<json>']`
- `af jira update <issue-key> [--summary "<text>"] [--description "<text>"] [--priority <name>] [--labels a,b,c] [--estimate <time>] [--remaining <time>] [--fix-version <v1,v2>] [--affected-version <v>] [--parent <issue-key>] [--clear-parent] [--field name=value]... [--field-json '<json>']`
- `af jira transition <issue-key> --to "<status>" [--resolution <name>] [--comment "<text>"] [--field name=value]...` — Screen fields, resolution, and a transition comment in one call
- `af jira assign <issue-key> --to <email>` (use `--to none` to unassign)
- `af jira comment <issue-key> --add "<text>" [--visibility <name>] [--internal] [--public]`
- `af jira comment edit <issue-key> <comment-id> --body "<text>" [--visibility <name>]`
- `af jira comment delete <issue-key> <comment-id>`
- `af jira attach <issue-key> <file>` — Attach a file (images, PDFs, etc.)
- `af jira move <issue-key> --to-project <KEY> [--type <name>]` — Move to another project (async; `--type` defaults to the current issue type)
- `af jira delete <issue-key>`
- `af jira watch <issue-key>` / `af jira unwatch <issue-key>` / `af jira vote <issue-key>` — All act as the current user

`--parent` sets the parent (subtask parent or epic — `parent` is the canonical field that replaced Epic Link); `--clear-parent` detaches it and wins if both are given. `--clear-parent` is provisional: behavior varies by project type.

`--visibility <name>` restricts a comment to a project role by default; prefix with `group:` to pick a group (`--visibility "group:Developers"`) or `role:` to force a role. `--internal` / `--public` are JSM only — they route the comment through the Service Desk API to add an internal note or a public reply.

### Worklog Operations

- `af jira worklog list <issue-key>` — List worklogs
- `af jira worklog add <issue-key> --time <duration> [--comment "<text>"] [--started "<timestamp>"]` — `--time` required (e.g. `2h`, `30m`); `--started` defaults to now and uses Jira's format `yyyy-MM-ddTHH:mm:ss.SSS+0000`
- `af jira worklog update <issue-key> <worklog-id> [--time <duration>] [--comment "<text>"] [--started "<timestamp>"]`
- `af jira worklog delete <issue-key> <worklog-id>`

### Bulk Operations (JQL-selected, async)

- `af jira bulk delete --jql "<query>"`
- `af jira bulk transition --jql "<query>" --to "<status>"`
- `af jira bulk edit --jql "<query>" --field-json '<json>'`

All three select issues with `--jql` (required) and run through Jira's asynchronous bulk endpoints, polled to completion. The selection is a single unpaginated search capped at 1000 issues — a JQL matching more than 1000 is silently truncated, so narrow the JQL (and re-run) rather than assuming everything matched was processed.

- **Bulk transition refuses screens.** If the target transition presents a screen or has required fields, the command errors out and points you at single-issue `af jira transition`, which can supply `--resolution`/`--field`. Bulk transition is for no-screen transitions only.
- **`bulk edit --field-json` is NOT the same shape as `create`/`update --field-json`.** It takes Jira's bulk-edit `editedFieldsInput` payload verbatim, not a plain map of custom fields. Consult the Jira bulk edit API for the shape.

### Agile Operations (Jira Software)

- `af jira boards [--project <key>]` — List boards
- `af jira sprints --board <id> [--state future,active,closed]` — List a board's sprints (`--board` required)
- `af jira sprint add <issue-key> --sprint <sprint-id>` — Move an issue into a sprint
- `af jira sprint remove <issue-key>` — Move an issue to the backlog
- `af jira rank <issue-key> --above <key>` / `--below <key>` — Reorder in the backlog (at least one is required; if both are passed, `--above` wins and `--below` is ignored)

### Link Operations

- `af jira link <issue-key> --to <issue-key> [--type "<name>"]` — Link two issues (default type: "Blocks")
- `af jira unlink <issue-key> --from <issue-key>` — Remove a link
- `af jira remote-link <issue-key> --url "<url>" --title "<text>"` — Add a remote link
- `af jira remote-link <issue-key> --remove <link-id>` — Remove a remote link

### Version Operations

- `af jira version-create --project <key> --name "<text>" [--description "<text>"] [--start-date YYYY-MM-DD] [--release-date YYYY-MM-DD] [--released]`
- `af jira version-update <id> [--name "<text>"] [--description "<text>"] [--start-date YYYY-MM-DD] [--release-date YYYY-MM-DD] [--released] [--unreleased]`
- `af jira version-delete <id> [--move-fix-issues-to <id>] [--move-affected-issues-to <id>]`

## Output Formats

- Default: Markdown
- JSON: Add `--json` flag

## Common Workflows

### View my assigned issues

```bash
af jira search "assignee = currentUser() AND status != Done ORDER BY priority DESC"
```

### Start working on an issue

```bash
af jira get PROJ-123
af jira transition PROJ-123 --to "In Progress"
af jira comment PROJ-123 --add "Starting work"
```

### Complete an issue

Close it **with a resolution** — a bare `--to "Done"` leaves the issue resolved with no resolution. `transition` carries the resolution and the comment in the same call:

```bash
af jira transition PROJ-123 --to Done --resolution Fixed --comment "Shipped in v1.2"
```

### Transition through a screen

`af jira transitions` shows which transitions present a screen and what fields they require. Supply those with repeatable `--field`:

```bash
# See what the transition needs
af jira transitions PROJ-123

# Supply screen fields alongside the resolution
af jira transition PROJ-123 --to Done --resolution "Won't Do" \
  --comment "Superseded by PROJ-456" \
  --field customfield_10050='{"id":"3"}'
```

`transition --field` has **different semantics from `create`/`update --field`**: the value is JSON-parsed when it parses (so `--field customfield_10050='{"id":"3"}'` sends an object) and kept as a raw string otherwise. It is passed straight through to the transition screen — no alias resolution, no schema-driven encoding.

### Create a bug with details

```bash
af jira create --project PROJ --type Bug --summary "Login fails on Safari" \
  --description "Users cannot log in using Safari 17. Error: 'Invalid session'" \
  --priority High --labels safari,auth,urgent
```

### Create a task with time estimate and version

```bash
af jira create --project PROJ --type Task --summary "Implement auth" \
  --estimate "4h" --fix-version "v1.0.0"
```

### Create a subtask

```bash
af jira create --project PROJ --type Sub-task --summary "Write unit tests" \
  --parent PROJ-123
```

### Link issues

```bash
# Block another issue
af jira link PROJ-123 --to PROJ-456

# Use a specific link type
af jira link PROJ-123 --to PROJ-456 --type "Relates"

# Remove a link
af jira unlink PROJ-123 --from PROJ-456
```

### Remote links

```bash
# List remote links
af jira remote-link PROJ-123

# Add a remote link (e.g., design doc, PR, external resource)
af jira remote-link PROJ-123 --url "https://example.com/doc" --title "Design Doc"

# Remove a remote link
af jira remote-link PROJ-123 --remove 10042
```

### Manage versions

```bash
# List versions
af jira versions PROJ

# Create a version
af jira version-create --project PROJ --name "v1.0.0" --release-date 2024-06-01

# Mark a version as released
af jira version-update 12345 --released
```

### Update time tracking

```bash
af jira update PROJ-123 --estimate "8h" --remaining "2h"
```

### Log work

Estimates (`--estimate`/`--remaining` on `update`) are projections; worklogs are the record of time actually spent.

```bash
# Log 2h against an issue (--started defaults to now)
af jira worklog add PROJ-123 --time 2h --comment "Debugging the session bug"

# Log against a specific start time
af jira worklog add PROJ-123 --time 90m --started "2024-06-01T09:00:00.000+0000"

# Review, correct, remove
af jira worklog list PROJ-123
af jira worklog update PROJ-123 10015 --time 3h --comment "Also covered the fix"
af jira worklog delete PROJ-123 10015
```

### Reparent an issue / move it under an epic

```bash
# Put the issue under an epic (or change a subtask's parent)
af jira update PROJ-123 --parent PROJ-100

# Detach it
af jira update PROJ-123 --clear-parent
```

### Manage comments

```bash
# Add a comment restricted to a project role, or to a group
af jira comment PROJ-123 --add "Internal note" --visibility Developers
af jira comment PROJ-123 --add "Internal note" --visibility "group:jira-developers"

# JSM: internal note vs. public reply to the customer
af jira comment PROJ-123 --add "Escalating to platform" --internal
af jira comment PROJ-123 --add "We've deployed a fix" --public

# Edit or delete (get comment ids from `af jira comment PROJ-123`)
af jira comment edit PROJ-123 10045 --body "Corrected: shipped in v1.3"
af jira comment delete PROJ-123 10045
```

### Sprints, boards, and backlog ranking (Jira Software)

```bash
# Find the board, then its sprints
af jira boards --project PROJ
af jira sprints --board 42 --state active,future

# Move an issue into a sprint, or back to the backlog
af jira sprint add PROJ-123 --sprint 108
af jira sprint remove PROJ-123

# Reorder the backlog
af jira rank PROJ-123 --above PROJ-456
af jira rank PROJ-123 --below PROJ-456
```

### Bulk operations over a JQL selection

```bash
# Close out a batch (no-screen transitions only — see below)
af jira bulk transition --jql "project = PROJ AND status = 'In Review' AND updated < -30d" \
  --to "In Progress"

# Delete a batch
af jira bulk delete --jql "project = PROJ AND type = Task AND labels = scratch"

# Bulk edit takes Jira's editedFieldsInput payload, NOT the create/update --field-json shape
af jira bulk edit --jql "project = PROJ AND labels = migrate" --field-json '<editedFieldsInput>'
```

`bulk edit` passes the `--field-json` object straight to Jira's bulk edit API as `editedFieldsInput`, and its top-level keys become the `selectedActions`. Look the shape up in Jira's bulk edit API docs — it is **not** the plain `{"customfield_10050": ...}` map that `create`/`update --field-json` accept, so don't copy one into the other.

If the bulk transition target presents a screen or has required fields, the command refuses and tells you to use single-issue `af jira transition` (which can pass `--resolution`/`--field`). Loop over the keys yourself in that case.

### Move an issue to another project

```bash
# --to-project is required; --type defaults to the current issue type
af jira move PROJ-123 --to-project OPS

# Change the issue type as part of the move
af jira move PROJ-123 --to-project OPS --type Task
```

This runs through the asynchronous bulk-move API and reports the polled task id and status.

### Watch or vote

```bash
af jira watch PROJ-123
af jira unwatch PROJ-123
af jira vote PROJ-123
```

All three act as the current user (the account behind `ATLASSIAN_EMAIL`).

### Custom fields

Custom fields are instance-specific (Story Points, Sprint, Severity, and any custom selectors configured by the Jira admin). Reference them by alias (from `af.json`), by display name (case-insensitive, must be unambiguous), or by raw `customfield_<digits>` id — in that precedence order.

```bash
# Discover fields — lists every custom field in the instance
af jira fields

# Project-and-type scope marks required fields with ✓ and lists allowed values
af jira fields --project PROJ --type Story

# Bust the cache (instance catalog is cached 24h, createmeta 1h)
af jira fields --refresh

# Create with required custom fields
af jira create --project PROJ --type Story --summary "Redesign login" \
  --field storyPoints=5 --field severity=High

# Update a custom field
af jira update PROJ-123 --field storyPoints=8

# Clear a custom field (empty value after `=`)
af jira update PROJ-123 --field severity=

# JSON escape hatch for complex shapes (cascading selects, raw overrides)
af jira create --project PROJ --type Story --summary "X" \
  --field-json '{"customfield_10050":{"value":"A","child":{"value":"A1"}}}'

# Show custom fields as extra columns when listing
af jira list PROJ --show-field storyPoints,severity
```

Encoding rules (applied per `--field`):

- `number` → numeric; `string`/`date`/`datetime` → raw string
- `option` → `{value: "..."}`; multi-select → array of those
- `user` → resolves email/name to `{accountId}` via `findUser`; multi-user splits on commas
- `version` → `{name: "..."}`; multi-version array
- sprint → numeric sprint ID only
- Epic Link → issue key string
- Empty value after `=` → `null` (clears the field)
- Unknown schema types pass through raw and warn to stderr — reach for `--field-json` if that doesn't encode correctly

These rules apply to `create --field` and `update --field` only. `transition --field` is a different flag with different semantics (raw pass-through, JSON-parsed when parseable), and `bulk edit --field-json` takes Jira's `editedFieldsInput` payload rather than the plain custom-field map that `create`/`update --field-json` take.

Optional aliases live in `af.json` and make commands more ergonomic:

```json
{
    "jira": {
        "customFields": {
            "storyPoints": { "id": "customfield_10016" },
            "sprint":      { "id": "customfield_10020", "type": "sprint" },
            "severity":    { "id": "customfield_10099" }
        }
    }
}
```

The CLI does **not** pre-validate required fields on create. Run `af jira fields --project PROJ --type <type>` first to see what Jira will require; otherwise the error message from Jira's 400 surfaces unchanged.

### Attach files to an issue

```bash
# Attach a screenshot
af jira attach PROJ-123 ./screenshot.png

# Attach multiple files
for f in ./audit/*.png; do
  af jira attach PROJ-123 "$f"
done
```

### Search examples

```bash
# My open issues
af jira search "assignee = currentUser() AND status != Done"

# Recent bugs in project
af jira search "project = PROJ AND type = Bug ORDER BY created DESC" --limit 10

# Unassigned issues
af jira search "project = PROJ AND assignee IS EMPTY"

# Issues updated this week
af jira search "project = PROJ AND updated >= -7d"

# High priority blockers
af jira search "priority = Highest AND status != Done"
```

## Tips

- **Discover valid values first**: Run `af jira transitions <key>` before transitioning (it shows screen fields too), `af jira types <project>` before creating, `af jira fields --project <key> --type <type>` before creating an issue that requires custom fields, and `af jira editmeta <key>` before updating one — `fields` is create-scoped, `editmeta` is the edit-scoped view of an existing issue
- **Close issues with a resolution**: `af jira transition <key> --to Done --resolution Fixed`. A bare `--to Done` leaves the issue with no resolution
- **Use `--json` for scripting**: Pipe output to `jq` for automation
- **Quote JQL queries**: Always wrap JQL in double quotes to handle spaces
- **Time format**: Use `"2h"`, `"1d"`, `"30m"` for estimates and for `worklog --time`
- **Clearing flags**: `--fix-version ""` clears versions; `--field name=` clears a custom field; `--clear-parent` detaches a parent
- **Alias custom fields in `af.json`** for nicer `--field` ergonomics; otherwise use the display name (case-insensitive, must be unambiguous) or the raw `customfield_<digits>` id
- **Dry-run bulk ops**: run the `--jql` through `af jira search` first to see exactly which issues `af jira bulk` will hit — bulk delete is not reversible

## Error Handling

- Errors print to stderr
- With `--json`: `{"error": "message"}`
- Exit codes: `0` success, `1` error

