acli · git:20260824.519befc · 2026-08-24 · sha256 156191c5df921729

acli git:20260824.519befcA

Immutable. This exact content is served forever at /api/v1/blob/156191c5df921729.

---
name: acli
description: "Atlassian CLI (official `acli` binary, v1.3+ as of 2026) for Jira Cloud, Confluence Cloud, and org admin tasks from the terminal. Use whenever the user wants to create, view, edit, transition, assign, clone, archive, comment on, link, or bulk-operate on Jira work items; list or manage projects, boards, sprints, filters, dashboards, or custom-field definitions; create or update Confluence spaces, pages, or blog posts; activate/deactivate users at the org level; or authenticate to Atlassian from a shell or CI pipeline. Triggers on: `acli`, Atlassian CLI, Jira from the terminal, Confluence from the terminal, bulk Jira operations, scripting Jira, automate Jira tickets, transition a bunch of issues, create issues from a JSON/CSV file, CI pipeline that touches Jira, log in to Jira CLI, switch Atlassian sites, API-token auth for Jira. Use this skill even when the user does not say the word `acli` — if the task is CLI-driven Jira or Confluence work, this is the right tool. Do NOT use for: Atlassian MCP server work (that is a different integration), REST-API-only workflows where no CLI is involved, Bitbucket command-line needs (acli does not cover Bitbucket yet), or the legacy Appfire/Bob Swift `acli` tool (a different product that happens to share the binary name). The Atlassian MCP server is OPT-IN, documented in docs/mcp/."
license: MIT
compatibility: [claude-code, cursor, codex, opencode]
allowed-tools: Bash(acli:*)
complementary_categories: [issue-tracker]
---

# Atlassian CLI (`acli`)

`acli` is Atlassian's official command-line tool for Jira Cloud, Confluence Cloud, and org admin operations. It replaces terminal-based Jira automation that previously required raw REST calls, and unifies Jira + Confluence + admin actions behind one binary with one credential store per product.

This skill teaches how to drive `acli` for any intent: one-off commands, batch mutations, scripted pipelines, and CI jobs. **Repo-specific integration** (how this skill plugs into the host repo's workflow, TMS modality, project conventions, anti-patterns) lives in the companion file `<repo-core>/references/acli-integration.md` — load it on demand. See "Navigation" below.

## Why this skill exists

`acli` has several traits that make it easy to misuse:

1. **Silent pagination truncation.** `workitem search` without `--paginate` returns the first page only — no warning. Scripts that count or iterate keys read the wrong number of items.
2. **Auth is per-product.** `acli jira auth login` does not authenticate `acli admin`, `acli confluence`, or `acli rovodev`. There is also a top-level `acli auth` for global OAuth (newer surface). Each scope has its own session.
3. **The "work item" vs "issue" split.** The CLI renamed commands (`jira issue` → `jira workitem`) but the JSON response still has a top-level `issues[]` array and CSV inputs still use `issueType`/`parentIssueId` spellings. Mixing old and new terminology in the same script works, but confuses readers.
4. **Unknown subcommands fail silently.** Typing `acli jira workflow --help` does NOT error — it falls back to `acli jira --help` with exit 0. So "no error" ≠ "command exists". Always verify by checking the help body actually changed.
5. **Hard limits the docs do not advertise.** `acli` cannot list custom fields, edit custom-field values on existing items, manage workflows, manage issue types, or touch project versions/components. See `references/gotchas.md`.

The body below covers the core that applies to almost every session. The `references/` directory holds the deep material — load only the one you need.

## Composable Skills (auto-resolved at skill entry)

`acli` is itself the canonical `issue-tracker` skill. The category typically has no T3 skills that overlap — `acli` is the tool surface, not a borrower of community skills.

Steps for protocol consistency:

1. Read `complementary_categories` from this skill's frontmatter (`issue-tracker`).
2. Resolve via the host repo's skill-registry cache (`.agents/skills/REGISTRY.md`, built by `scripts/build-skill-registry.ts`). Fallback: scan the session-start `system-reminder` skill list.
3. Apply the threshold rule per the host repo's skill-composition strategy doc (T1 / T3 silent; T4 ASK).
4. The Atlassian MCP fallback documented below is OPT-IN, not a skill — enable manually via `docs/mcp/`.

Expected matches: typically none. Repo-specific composability (which workflow skills load this) lives in `<repo-core>/references/acli-integration.md` §Composability.

Skip step if the catalog is unavailable; log `skill_resolution: "fallback-inline"` plus `missing: [<categories>]` per the strategy doc's composability fallback contract.

## Fallback: Atlassian MCP

> **Opt-in only**: this MCP is NOT enabled in the default boilerplate. To use it, copy the atlassian block from `docs/mcp/<agent>.template.*` into `.mcp.json` / `opencode.jsonc`, ensure `ATLASSIAN_*` in `.env` are set, and restart the agent. Behavior below applies only after opt-in.

If `acli` is not installed or authenticated, fall back to the Atlassian MCP server (MCP tool namespace: `mcp__atlassian__*` or similar — check the MCP tool list for the exact prefix in the current environment).

**When to prefer MCP over acli**:

- `acli` binary is not installed in the environment.
- `acli` auth fails and cannot be fixed in the current session.
- The operation is one of the documented `acli` blind spots: enumerate custom fields, edit custom-field values on existing work items, manage workflows / issue types / priorities / resolutions / project versions / components, upload attachments, add watchers, add an item to a sprint.

**When to prefer acli over MCP**:

- Bulk operations (acli consumes far fewer tokens per call).
- Scripting / CI pipelines.
- Operations that return large result sets (MCP payloads inflate token usage).

**Coverage parity**: MCP and `acli` overlap for issues, projects, boards, sprints, comments, and basic Confluence ops. For org-admin user lifecycle and Confluence space CRUD, `acli` is more direct. For schema/admin reads (field catalog, workflow definitions), MCP/REST is the only viable path.

## Command structure

```
acli <product> [<feature>] <action> [flags]
```

| Product       | Purpose                                                          |
| ------------- | ---------------------------------------------------------------- |
| `jira`        | Jira Cloud — work items, projects, boards, sprints, filters, dashboards, custom-field definitions |
| `confluence`  | Confluence Cloud — spaces (CRUD), blog posts, page view          |
| `admin`       | Organization admin — API-key auth, user lifecycle                |
| `auth`        | Global OAuth (cross-product, newer top-level surface)            |
| `rovodev`     | Rovo Dev AI coding agent (separate beta product)                 |
| `feedback`    | Send feedback or a bug report to Atlassian                       |
| `config`      | Atlassian Government Cloud configuration (`gov-cloud`)           |
| `completion`  | Generate shell-autocompletion script (bash / zsh / fish / powershell) |

Every level has `--help`. Use it aggressively when unsure:

```bash
acli --help
acli jira --help
acli jira workitem --help
acli jira workitem create --help
```

## Quick start

```bash
# 1. Authenticate against a site using an API token (scriptable path)
echo "$ATLASSIAN_API_TOKEN" | acli jira auth login \
  --site "<your-site>.atlassian.net" \
  --email "you@example.com" \
  --token

# 2. Verify
acli jira auth status

# 3. Create a work item
acli jira workitem create --project "{{PROJECT_KEY}}" --type "Task" --summary "Draft the Q3 OKRs"

# 4. Search with JQL — ALWAYS pass --paginate or --limit explicitly
acli jira workitem search --jql "project = {{PROJECT_KEY}} AND status = 'To Do'" --paginate --json

# 5. Transition one or many
acli jira workitem transition --jql "project = {{PROJECT_KEY}} AND assignee = currentUser()" \
  --status "In Progress" --yes --ignore-errors
```

> **Repo-specific quick start**: when the host repo defines its own workflow (status names, project keys, slug-resolved custom fields), see `<repo-core>/references/acli-integration.md` — it documents the project-flavored variant of the steps above.

## Top-level command map

### Jira (`acli jira`)

| Subcommand   | What it covers                                            |
| ------------ | --------------------------------------------------------- |
| `auth`       | login · logout · status · switch — API-token or OAuth     |
| `workitem`   | archive · assign · attachment (list / delete) · clone · comment (create / delete / list / update / visibility) · create · create-bulk · delete · edit · link (create / delete / list / type) · search · transition · unarchive · view · watcher (list / remove) |
| `project`    | archive · create · delete · list · restore · update · view |
| `board`      | create · delete · get · list-projects · list-sprints · search |
| `sprint`     | create · delete · list-workitems · update · view          |
| `filter`     | add-favourite · change-owner · get · get-columns · list · reset-columns · search · update |
| `dashboard`  | search                                                    |
| `field`      | cancel-delete · create · delete · update — **custom-field DEFINITIONS only**, NOT values, and **no listing** |

### Confluence (`acli confluence`)

| Subcommand | What it covers                                                       |
| ---------- | -------------------------------------------------------------------- |
| `auth`     | login · logout · status · switch — same model as `jira auth`         |
| `space`    | archive · create · list · restore · update · view (full CRUD)        |
| `blog`     | create · list · view                                                 |
| `page`     | view (read-only as of v1.3.18 — page CRUD not yet exposed)           |

### Admin (`acli admin`)

| Subcommand | What it covers                                |
| ---------- | --------------------------------------------- |
| `auth`     | login · logout · status · switch — API key    |
| `user`     | activate · deactivate · delete · cancel-delete |

## The selector pattern (the thing to internalize)

Most mutating `workitem` commands (`edit`, `transition`, `assign`, `archive`, `clone`, `comment create`) accept **one of** these target selectors:

| Selector            | When to use                                                    |
| ------------------- | -------------------------------------------------------------- |
| `--key KEY-1,KEY-2` | You already know the exact keys                                |
| `--jql "..."`       | You want everything matching a JQL query                       |
| `--filter 10001`    | You want to reuse a saved Jira filter                          |
| `--from-file f`     | You have a file listing keys (`archive`/`unarchive`/`assign`)  |

When the selector matches many items, the command is **a batch operation**. Two flags almost always matter:

- `-y, --yes` — skip the interactive confirmation prompt. Required in CI; if omitted the command hangs waiting on stdin. **Note:** this flag does NOT exist on `admin user delete` / `admin user cancel-delete` (use `--ignore-errors` there instead).
- `--ignore-errors` — do not abort the batch when a single item fails.

## Output and piping

All list/search/view commands support three shapes:

- default table (human-readable)
- `--json` (for `jq` / scripts)
- `--csv` (spreadsheet-friendly)

Example pipe patterns:

```bash
# Count only
acli jira workitem search --jql "project = {{PROJECT_KEY}}" --count

# Save full result set to CSV
acli jira workitem search --jql "project = {{PROJECT_KEY}}" --paginate --csv > team.csv

# Extract a single field with jq
acli jira workitem view {{PROJECT_KEY}}-123 --json | jq '.fields.summary'
```

The JSON shape from `workitem search` has a top-level `issues` array (not `workitems`) — the Jira REST v3 wire format shows through.

## Publishing rich text (the default workflow)

Jira stores rich-text content (descriptions, comments, and any rich-text field) as **ADF — Atlassian Document Format**, a JSON tree of typed nodes (`heading`, `paragraph`, `bulletList`, `orderedList`, `codeBlock`, `blockquote`, `rule`, `table`, `panel`, `expand`) with inline marks (`strong`, `em`, `code`, `link`, `strike`).

`acli` accepts ADF JSON in every rich-text input. `acli` **never** converts markdown — passing `# Heading` to `--description` or `--body` stores the literal string `# Heading` wrapped in a single ADF paragraph.

> **⚠️ Asymmetry: `create` supports custom-field rich text, `edit` does NOT.**
> `acli workitem create --from-json` accepts custom fields via `additionalAttributes` (ADF doc payloads work). `acli workitem edit --from-json` **hard-rejects every custom-field shape** (`additionalAttributes`, `fields`, flat `customfield_X`) with exit 1 + `unknown field` error. No silent drop, no escape hatch in the binary. To update or correct a rich-text custom field on an **existing** work item, you MUST use the REST PUT workaround documented below — `acli` cannot do it.

To publish anything richer than plain prose, use this three-step workflow by default:

```
1. Author the content in Markdown.
2. Convert MD → ADF JSON using scripts/md-to-adf.ts.
3. Pass the ADF JSON to the matching acli flag — or, for cases acli cannot cover, into a REST body.
```

### The bundled converter

Location: `.agents/skills/acli/scripts/md-to-adf.ts`. Runtime: Bun.

CLI usage:

```bash
bun .agents/skills/acli/scripts/md-to-adf.ts input.md output.adf.json
# stdin form
cat input.md | bun .agents/skills/acli/scripts/md-to-adf.ts - output.adf.json
# stdout form (omit output arg)
bun .agents/skills/acli/scripts/md-to-adf.ts input.md > output.adf.json
```

Programmatic usage (when batching across many fields or many work items in one script):

```typescript
import { mdToAdf, validateAdf } from "./.agents/skills/acli/scripts/md-to-adf.ts";
const adf = mdToAdf(markdownString);  // returns { type: "doc", version: 1, content: [...] }
const { valid, errors } = validateAdf(adf);  // gate ANY ADF before publishing
```

**Covered markdown subset**: headings 1–6, bullet lists, ordered lists, **nested lists** (indentation-based), **GFM tables** (`| a | b |` + `|---|---|` separator), **panels** (GitHub-alert blockquotes), **expand blocks** (`<details><summary>`), **Jira-native emoji** (`:short_name:`), **status lozenges** (`{status:color|TEXT}`), **mentions** (`@[Name](accountId)`), fenced code blocks (with optional language tag), inline code, bold, italic (snake_case-safe), strikethrough, links, blockquotes, horizontal rule, paragraphs.

Rich-block syntax cheat-sheet:

| Markdown you write | ADF node produced |
|---|---|
| `\| H1 \| H2 \|` then `\| --- \| --- \|` then body rows | `table` (header row → `tableHeader`, body → `tableCell`; inline marks work inside cells; `\|` escapes a literal pipe) |
| `> [!NOTE]` / `[!INFO]` (blue) · `[!TIP]` / `[!SUCCESS]` (green) · `[!IMPORTANT]` (purple) · `[!WARNING]` (yellow) · `[!CAUTION]` / `[!ERROR]` (red), then `> body` lines | `panel` with `panelType` `info` / `success` / `note` / `warning` / `error`. Body re-parsed as Markdown (can hold lists, code, etc.) |
| Two-space (or deeper) indentation under a list item | nested `bulletList` / `orderedList` inside that `listItem`; depth = indent width; bullet/ordered mix per level |
| `<details>` / `<summary>Title</summary>` / body / `</details>` | `expand` with `attrs.title`; body re-parsed as Markdown |
| `:white_check_mark:` `:x:` `:warning:` … any `:short_name:` | `emoji` node (Jira resolves the shortName; curated status marks also carry a Unicode `text` fallback). Inline code is parsed first, so a colon inside `` `code` `` is safe |
| `{status:green\|DONE}` (colors: `neutral` `purple` `blue` `red` `yellow` `green`) | `status` node — the coloured lozenge/pill for transition states. `localId` not required (Jira injects none on publish) |
| `@[Display Name](accountId)` | `mention` node. The `accountId` is supplied explicitly (resolve it via `/rest/api/3/user/search` — see `references/adf-authoring-style.md` §mentions); a bare `@name` is NOT converted |

**Media (images / videos)** are NOT Markdown — `![](path)` does not work, because an ADF media node needs the opaque media-services UUID of an uploaded file. Use the bundled helper `scripts/jira-attach-media.ts` instead (upload → resolve UUID → emit/publish the `mediaSingle > media` node). Example: `bun scripts/jira-attach-media.ts BUG-123 ./repro.png --caption "Repro step 3" --publish`. Full recipe + when-to-use in `references/adf-authoring-style.md` §media.

**Out of scope** (extend the converter if your project needs them): `nestedExpand` (expand inside a table cell).

> **This section covers HOW Markdown becomes ADF. For WHEN to reach for a table vs a panel vs a nested list — i.e. how to make field content visually scannable instead of flat prose — see `references/adf-authoring-style.md`.** Workflow skills cite that file at each point they fill a Jira rich-text field.

### Validation gate (fail fast before Jira)

The converter **validates its output by default** against an embedded ADF allowlist, then refuses to write and exits non-zero if the document is invalid. This turns an opaque Jira `HTTP 400 INVALID_INPUT` at publish time into a node-level diagnostic at author time. The gate is **zero-dependency** — it does NOT use `@atlaskit/adf-utils` (that package transitively pulls ProseMirror + Statsig and breaks the converter's zero-dep contract). The rules are inlined in `md-to-adf.ts`.

What it catches: unknown node types, unknown / invalid marks, `code` co-occurring with `strong`/`em`/`strike`/`underline`/`subsup`/`textColor` (the HTTP 400 combined-marks bug), `heading` level outside 1–6, missing `link` `href`, empty `text` nodes, illegal containment (e.g. a `paragraph` directly under a `bulletList`), and a malformed root (`type` ≠ `doc` or `version` ≠ 1).

```bash
# validate is on by default during conversion; bypass with --no-validate
bun .agents/skills/acli/scripts/md-to-adf.ts input.md out.adf.json --no-validate

# gate an ALREADY-assembled ADF doc (jq create payload field, or a REST PUT body)
bun .agents/skills/acli/scripts/md-to-adf.ts --check field.adf.json   # exit 0 valid, 1 invalid
```

**Recommended habit**: after splicing ADF into a `--from-json` create payload or a REST `PUT` body (where the wrapper is assembled outside the converter), run `--check` on each ADF field before sending. The gate is necessary but not sufficient — a round-trip `GET` of the field after write is still the only way to catch server-side coercion (Jira silently drops some invalid nodes).

### Recipe by Jira surface

| Surface | How to publish ADF | Notes |
|---|---|---|
| `description` on `workitem create` | `--from-json` payload, `description` key holds an ADF doc | Custom-field values live in `additionalAttributes` of the same payload, same ADF shape |
| `description` on `workitem edit` | `--description-file <file>` accepts a JSON file containing an ADF doc | `acli` auto-detects ADF vs plain text by file content |
| Rich-text custom field on `workitem create` | `additionalAttributes.customfield_NNNNN` = ADF doc inside `--from-json` | Same shape as `description` |
| Rich-text custom field on an existing item | **`acli` cannot do this — use REST PUT workaround.** `PUT /rest/api/3/issue/{KEY}` with `{"fields": {customfield_NNNNN: <ADF>}}` via `curl` | `acli workitem edit` hard-rejects `additionalAttributes`, `fields`, and flat `customfield_X` with `✗ Error: json: unknown field …`. Confirmed empirically. See gotcha #4 + dedicated workaround section below. |
| Comment create | `comment create --body-file <file>` (alias `-F`) accepts ADF | The `--body` (plain) flag remains plain text only |
| Comment update | `comment update --body-adf <file>` | Dedicated ADF flag |

### Worked end-to-end example

```bash
# 1. Author each rich-text field as Markdown
cat > /tmp/desc.md <<'MD'
## User Story

- As a user
- I want X
- So that Y

## Context

Some context paragraph with **bold** and `inline_code`.
MD

cat > /tmp/ac.md <<'MD'
## Scenario: happy path

Given a valid input
When the user submits
Then the response is 200 OK
MD

# 2. Convert each MD file to ADF JSON
bun .agents/skills/acli/scripts/md-to-adf.ts /tmp/desc.md /tmp/desc.adf.json
bun .agents/skills/acli/scripts/md-to-adf.ts /tmp/ac.md   /tmp/ac.adf.json

# 3. Splice the ADF docs into the create-from-json payload
jq -n \
  --arg pk "{{PROJECT_KEY}}" \
  --slurpfile desc /tmp/desc.adf.json \
  --slurpfile ac   /tmp/ac.adf.json \
  '{
    projectKey: $pk,
    type: "Story",
    summary: "Example summary",
    description: $desc[0],
    labels: ["example"],
    additionalAttributes: {
      customfield_NNNNN: $ac[0]
    }
  }' > /tmp/story.json

# 4. Submit
acli jira workitem create --from-json /tmp/story.json --json
```

### Batch pattern (many work items, many rich fields)

When the task is to populate N work items with M rich-text fields each, the converter scales linearly with negligible overhead. Recommended pattern:

1. Write one generator script (`generate.ts`) that holds the per-field Markdown content for every item as inline string literals.
2. The script imports `mdToAdf` and converts every field in-process — no shell hop per conversion.
3. The script writes one `create --from-json` payload per item (`/tmp/item-N.json`).
4. A shell loop runs `acli jira workitem create --from-json /tmp/item-N.json --json` per file, capturing the new key from stdout.
5. For comments, follow the same approach: write the comment Markdown inline, convert in-process, post with `acli jira workitem comment create -k <KEY> -F /tmp/comment-N.adf.json`.

This pattern scales cleanly to dozens of items in one run. The bottleneck is authoring quality, not the conversion mechanic.

### WORKAROUND: Editing rich-text custom fields on existing work items (REST PUT)

This is the **only** working path as of acli v1.3.18 — there is no acli-native channel for editing custom-field values on existing items. The recipe below is the turnkey workaround.

**Prerequisites.** Two env vars must be exported in the current shell. They are loaded automatically by the project tooling (`bun claude`, `bun opencode`, or `direnv`) from `.env`:

- `ATLASSIAN_EMAIL` — the API-token owner's email
- `ATLASSIAN_API_TOKEN` — the API token paired with the email

The site host is **not** an env var. It lives in `.agents/project.yaml` ->
`issue_tracker.atlassian_url`, and the recipes below read it with
`$(bun run --silent jira:url)`. It was pulled out of `.env` because a stale copy
inherited from the parent shell silently shadowed the file and pointed the sync
scripts at a dead Jira site. Never reintroduce `ATLASSIAN_URL` as a shell
variable in a recipe — resolve the host, do not interpolate it.

**Recipe.**

```bash
# 1. Author the new value as Markdown
cat > /tmp/new.md <<'MD'
## New content
- with **bold**, `inline code`, and a [link](https://example.com)
MD

# 2. Convert MD → ADF
bun .agents/skills/acli/scripts/md-to-adf.ts /tmp/new.md /tmp/new.adf.json

# 3. Wrap the ADF doc in the REST `{ "fields": { ... } }` envelope
#    (NOTE: same ADF payload acli would consume; only the wrapper key changes)
jq -n --slurpfile adf /tmp/new.adf.json \
  '{fields: {customfield_NNNNN: $adf[0]}}' > /tmp/put.json

# 4. PUT against the issue
curl -sS -w "\nHTTP %{http_code}\n" \
  -u "$ATLASSIAN_EMAIL:$ATLASSIAN_API_TOKEN" \
  -X PUT "$(bun run --silent jira:url)/rest/api/3/issue/{{PROJECT_KEY}}-123" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data-binary @/tmp/put.json
# Expected: HTTP 204 (Jira returns no body on a successful PUT)
```

**Reference.** Official Atlassian REST v3 PUT endpoint:
<https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-put>

**Empirical proof this is the only path.** Three variants tested against `acli workitem edit --from-json` on a real workitem:

| Payload shape sent to `acli edit` | Result |
|---|---|
| `{issues:[...], additionalAttributes:{customfield_X:<ADF>}}` | `✗ Error: json: unknown field "additionalAttributes"` · exit 1 |
| `{issues:[...], fields:{customfield_X:<ADF>}}` | `✗ Error: json: unknown field "fields"` · exit 1 |
| `{issues:[...], customfield_X:<ADF>}` | `✗ Error: json: unknown field "customfield_X"` · exit 1 |

Same ADF doc through REST PUT: HTTP 204 OK.

**Batch variant.** Loop the recipe per `--data-binary @/tmp/put-N.json` and capture HTTP codes:

```bash
for KEY in {{PROJECT_KEY}}-1 {{PROJECT_KEY}}-2 {{PROJECT_KEY}}-3; do
  status=$(curl -sS -o /dev/null -w "%{http_code}" \
    -u "$ATLASSIAN_EMAIL:$ATLASSIAN_API_TOKEN" \
    -X PUT "$(bun run --silent jira:url)/rest/api/3/issue/$KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/put-"$KEY".json)
  echo "$KEY -> HTTP $status"
done
```

**When this becomes unnecessary.** If Atlassian adds an `additionalAttributes`-style channel to `acli workitem edit`, retire this workaround and update the recipe table.

### Why this is the default

- Authoring in Markdown is fast, reviewable in pull requests, diffable.
- The conversion is deterministic — the same Markdown always produces the same ADF tree.
- One workflow covers every rich-text surface uniformly: descriptions, comments, custom fields, all the same three steps.
- Identifier-heavy prose (snake_case, kebab-case) survives the conversion because the italic detection has word-boundary guards.

## Anti-patterns — NEVER do these (tool-level)

These are tool-level anti-patterns intrinsic to the `acli` binary and its REST companion. They apply regardless of host repo. Gotchas describe *surprising behavior to remember*; anti-patterns describe *actions to refuse outright*. Both apply.

- **T1.** NEVER hand-author raw ADF JSON for descriptions, comments, or rich-text custom fields. Use `scripts/md-to-adf.ts` — deterministic, diffable, snake_case-safe, and avoids the combined-marks bug (inline `code` co-occurring with `strong`/`em` causes HTTP 400).
- **T2.** NEVER hardcode Jira `customfield_NNNNN` IDs in scripts or AI output that consumes `acli`. Resolve via the host project's slug catalog (see the host repo's `acli-integration.md`). IDs differ per workspace; slugs travel.
- **T3.** NEVER assume `acli` accepts custom-field input on `workitem edit`. It hard-rejects every shape (`additionalAttributes`, `fields`, flat `customfield_X`) with exit 1. Use the REST `PUT /rest/api/3/issue/{KEY}` workaround documented above — there is no acli-native path.
- **T4.** NEVER run a bulk `acli` mutation (transition, edit, comment, link, archive) without first verifying `acli jira auth status`. Silent auth expiry cascades into HTTP 401s mid-loop, leaving the batch half-applied with no clean rollback.

> **Repo-specific anti-patterns** (workflow abstraction, project-key portability, TMS modality boundaries, prod-workspace safety, CI batching, version pinning, sync-script auth) live in `<repo-core>/references/acli-integration.md`. Load it whenever a session touches the host repo's Jira workflow.

## Six gotchas to keep in mind always

1. **`--paginate` is opt-in.** Default limit is server-side (30–50 depending on command). No warning on truncation. If you are counting, iterating, or making decisions based on the result, pass `--paginate`.
2. **Custom fields on `workitem create` go through `additionalAttributes` in `--from-json`.** Numeric IDs only (`customfield_NNNN`), no name-addressing. Documented value shapes in the `create` template are: `{"value": "..."}` (single-select), bare number, bare string. **`workitem edit` actively REJECTS custom-field input — hard error, exit 1, not a silent drop** (empirically confirmed across `additionalAttributes`, `fields`, and flat `customfield_X` shapes). For editing custom-field values on existing items, the **only** working path is REST `PUT /rest/api/3/issue/{KEY}` via `curl` using the session env vars — see the "WORKAROUND" subsection in "Publishing rich text" above, plus `references/gotchas.md` §4 and `references/workitem.md`.
3. **`acli` cannot enumerate custom fields.** `acli jira field` only does create/update/delete/cancel-delete. To discover field IDs, use `workitem view --json | jq` against an item that has the field set, or call `GET /rest/api/3/field` directly. There is no in-CLI listing. Host repos typically cache the catalog under `.agents/` and resolve fields by slug — see `<repo-core>/references/acli-integration.md`.
4. **Transitions match by status name, not transition ID.** When two transitions lead to the same status with different validators, the CLI picks one and may fail. No `--transition-id` escape hatch exists — fall back to REST if this hits.
5. **Trace IDs are the only debug signal.** An `unexpected error, trace id: XXXXXXXX` line is all you get on backend failures. Capture and log the trace ID always; Atlassian Support needs it.
6. **`workitem link create` flag names are misleading — `--out` and `--in` are EMPIRICALLY INVERTED relative to Jira's outward/inward semantics.** Running `acli jira workitem link create --out X --in Y --type Dependencies` produces "**Y** depends on **X**" — NOT "X depends on Y" as the flag names suggest. Y becomes the outward party (the one that performs the outward verb, e.g. "depends on" / "blocks" / "causes"); X becomes the inward party. Confirmed empirically against Dependencies; the same inversion applies to ALL outward-asymmetric link types (Blocks, Blocking, Causes, Duplicate, Cloners, Defect, Test, Test Automation, Test Design, Test Execute). Symmetric types (Relates) are immune — direction is lost either way. **Reverse-mapping rule of thumb**: `--out` takes the PREREQUISITE (the inward partner in Jira's UI); `--in` takes the DEPENDENT (the outward partner in Jira's UI). **Mandatory verification after every link create**: run `acli jira workitem link list --key <expected-dependent> --json` and confirm the response shows `outwardIssueKey: <expected-prerequisite>`. If the direction is wrong, delete the link and recreate with swapped flags. Deep recipe + per-type mapping table → `references/workitem.md`.

## Top-level utilities

Quick-reference for the top-level surface that doesn't fit under a product. None of these need a separate reference file — they're documented here in full.

### `acli completion` — shell autocompletion

```bash
acli completion bash       > /etc/bash_completion.d/acli
acli completion zsh        > "${fpath[1]}/_acli"
acli completion fish       > ~/.config/fish/completions/acli.fish
acli completion powershell > acli.ps1
```

Each subcommand prints a shell script to stdout. Pipe to the location your shell expects (above are the conventional paths).

### `acli feedback` — report a problem to Atlassian

```bash
acli feedback \
  --summary "JSON shape on edit --generate-json is misleading" \
  --details "The template doesn't include additionalAttributes for custom fields..." \
  --email "you@example.com" \
  --time "1h" \
  --attachments error.log,trace.txt
```

Flags: `-s, --summary` (required-ish), `-d, --details` (required-ish), `-e, --email`, `-t, --time` (estimated timeframe like `1h`, `15m`), `-a, --attachments` (multiple files).

### `acli auth` — global OAuth (newer surface)

A top-level OAuth login that authenticates across products in one step. Distinct from per-product `jira auth` / `admin auth` / `confluence auth`. Use the per-product login for token-based CI; use the global one for interactive multi-product browsing.

```bash
acli auth login           # interactive OAuth
acli auth status
acli auth switch
acli auth logout
```

See `references/auth.md` for the full auth model.

### `acli config gov-cloud` — Atlassian Government Cloud

```bash
acli config gov-cloud --enable
acli config gov-cloud --status
```

Niche — only relevant if your org is on Atlassian Government Cloud. Not used in standard commercial Jira/Confluence.

## Navigation — when to load which reference

Load the reference that matches the user's current need. Do not preload all of them.

| If the user wants to…                                                | Load                                        |
| -------------------------------------------------------------------- | ------------------------------------------- |
| Log in, switch sites, handle tokens, authenticate in CI              | `references/auth.md`                        |
| Work with Jira tickets (create, edit, transition, search, bulk, comments, links, watchers, custom-field shapes) | `references/workitem.md`  |
| Manage projects, boards, sprints, filters, dashboards, custom-field definitions | `references/project-board-sprint.md` |
| Work with Confluence spaces, blogs, pages                            | `references/confluence.md`                  |
| Run org-level admin tasks (API key, user lifecycle)                  | `references/admin.md`                       |
| Pipe output, produce JSON/CSV, dry-run, run on CI/CD                 | `references/output-and-automation.md`       |
| Diagnose surprising behavior, known bugs, REST fallback points       | `references/gotchas.md`                     |
| Publish rich text to descriptions, comments, or custom fields        | Inline section "Publishing rich text" + `scripts/md-to-adf.ts` |
| Make Jira field content visually excellent (when to use tables / panels / nested lists for readability) | `references/adf-authoring-style.md` |
| Plug `acli` into the host repo's workflow (TMS modality, slug catalog, project conventions, anti-patterns specific to this repo) | `<repo-core>/references/acli-integration.md` |

## Working style

- **Default to Markdown authoring for any rich-text field.** Never pass raw markdown to `--description`, `--body`, or any custom-field value — `acli` does not convert markdown. Use `scripts/md-to-adf.ts` to produce ADF, then pass the JSON. See "Publishing rich text" above.
- **Prefer API-token auth in scripted contexts.** `--web` / OAuth is for humans at a terminal.
- **Always pass `--yes` in CI** for any mutating command (where the flag exists).
- **Always pass `--paginate`** when a downstream script consumes the result.
- **Scaffold complex payloads with `--generate-json`** (create, edit, project create, project update, link create, create-bulk). Pipe to a file, edit, submit with `--from-json`. Note: `--generate-json` is **static** — it does NOT introspect the actual project schema, so for custom-field shapes you may need to view a real item.
- **Capture the trace ID on any failure** and surface it when reporting to the user.
- **Do not invent flags.** When unsure, run `acli <path> --help` — it is authoritative and version-pinned to the installed binary. Convention: every multi-word flag is **kebab-case** (`--from-json`, `--searcher-key`, `--filter-id`, `--order-by`). camelCase variants will fail.
- **Verify subcommand existence before assuming.** Unknown subcommands silently fall back to parent help with exit 0 — they do NOT error. Read the help body, don't trust the exit code.
- **Know what `acli` cannot do.** All of the following require REST or MCP — `acli` does not cover them as of v1.3.18:
  - Enumerate custom fields (`field` has no `list`).
  - Edit custom-field values on existing work items (`workitem edit` does not document custom-field input).
  - Manage workflows, workflow schemes, statuses, or transition definitions.
  - Manage issue types, priorities, resolutions, project versions, project components.
  - Add a work item to a sprint (`JRACLOUD-97107`).
  - Upload attachments, add watchers.
  - Retrieve the cached auth token for reuse in another tool.
  - Bitbucket operations (out of scope entirely).
  - Confluence page CRUD beyond `page view` (as of v1.3.18 — space and blog have full CRUD).

  See `references/gotchas.md` for the full list with REST recipes.

## Installation (reference only)

Users usually already have `acli` installed. If not, point them at:

- Official guide: https://developer.atlassian.com/cloud/acli/guides/install-acli/
- macOS: `brew tap atlassian/homebrew-acli && brew install acli`
- Linux (Debian/Ubuntu): `apt install acli` (after adding the Atlassian apt repo)
- Linux (RHEL/Fedora): `yum install acli` (after adding the Atlassian yum repo)
- Windows: PowerShell `curl` install (no Chocolatey/MSI yet)
- CI one-liner (Linux): `curl -LO "https://acli.atlassian.com/linux/1.3.18/acli_linux_amd64/acli" && chmod +x acli`

Pin to a version URL in production pipelines — `latest/` has caused same-day mass failures. Each release is supported for six months. Run `acli --version` to check.