| name | jira-acli |
| description | Operate David's Jira Cloud (bellaslainte.atlassian.net) from the terminal with the official Atlassian CLI `acli`. Use whenever the task involves reading or writing a Jira board/project, creating/editing/transitioning/deleting work items, populating a Kanban board (e.g. from a Google Sheet), running JQL searches, or installing/authenticating acli. Captures the non-obvious gotchas: search defaults to 30 results, create-bulk caps at 50 and silently drops labels + ignores description in JSON, edit --labels appends, and the KAN board's columns are Idea/In Progress/Done. |
Jira via Atlassian CLI (acli)
The official Atlassian CLI. Binary: /opt/homebrew/bin/acli (on PATH). Verify with acli --version.
Environment (David's Jira)
- Site:
bellaslainte.atlassian.net
- Project
KAN ("Team Bella", software, team-managed) — board id 2. Also SAM1 (id 1, example, ignore).
- KAN columns/statuses:
Idea → In Progress → Done. New items land in Idea (the backlog column), NOT "To Do". Transition by the exact status name.
- Work item types on KAN:
Task, Feature (also standard: Epic, Story, Bug, Subtask).
- Owners in the sheets are people's names, not Jira accounts — only
david is a real user, so owners go in the description, not --assignee.
Install (already done; for a fresh machine)
brew tap atlassian/homebrew-acli
brew trust atlassian/acli
brew install acli
Direct download alternative: curl -LO "https://acli.atlassian.com/darwin/latest/acli_darwin_arm64/acli" (Apple Silicon), then chmod +x + move to PATH.
Auth — browser OAuth, David runs it himself
acli jira auth login --web
Per David's standing preference, hand him the command (pbcopy it) and let him run it — don't invoke the browser login from the Bash tool. Once cached, I can drive all read/write commands. Auth is NOT API-token based here.
Command map
| Action | Command |
|---|
| List boards | acli jira board search |
| Project info | acli jira project view --key KAN |
| Search items | acli jira workitem search --jql "project = KAN ORDER BY status" --limit 100 --json |
| View one item | acli jira workitem view KAN-7 --fields summary,labels,status --json |
| Create one | acli jira workitem create --summary "..." --project KAN --type Task --description "..." --label "consent" --assignee @me |
| Bulk create | acli jira workitem create-bulk --from-csv issues.csv --yes |
| Edit | acli jira workitem edit --key "KAN-1,KAN-2" --summary "..." --description "..." --labels "x" --yes |
| Transition (status/column) | acli jira workitem transition --key "KAN-1,KAN-2" --status "In Progress" --yes (or --jql "...") |
| Assign | acli jira workitem assign --key "KAN-1,KAN-2" --assignee "@me" --yes (@me, an email, or default) |
| Un-assign | acli jira workitem assign --key "KAN-1" --remove-assignee --yes (default assigns the project default, it does NOT clear) |
| List watchers | acli jira workitem list-watchers --key KAN-1 --json |
| Delete | acli jira workitem delete --key "KAN-1,KAN-2" --yes |
Watchers & assignee mechanics (learned 2026-06-30)
- The issue CREATOR auto-watches every issue they create. Bulk-created cards therefore all have the creator (david) as a watcher already — to make someone a watcher-but-not-owner, you often just need to NOT assign them (or un-assign), and they keep watching.
- Assigning a user auto-adds them as a watcher. Un-assigning does NOT remove the watch.
acli cannot ADD a watcher — workitem watcher only has list (deprecated) + remove. To add another user as watcher, POST the Jira REST API with their accountId as the raw JSON-string body:
POST /rest/api/3/issue/{KEY}/watchers, Content-Type: application/json, body = JSON.stringify(accountId) → 204. Run it from an authenticated browser tab on the Jira origin (Claude-in-Chrome javascript_tool, return a Promise — top-level await is NOT available in that REPL). Get the accountId from list-watchers --json or view --fields assignee --json after assigning them once.
- Owner-vs-watcher rule used for Team Bella: a person is assignee only if their name is FIRST in the sheet's Owner field (split on
+//, strip (offline)-style annotations); if mentioned but not first → watcher. Only applied to users who actually have Jira access. Don't silently override a teammate's own self-assignment — flag it instead.
What acli CANNOT do (use the Jira web UI or REST API instead)
- Create, rename, reorder, or delete board columns / workflow statuses. There is no
workflow/status command and no raw-API passthrough. board create only builds a board from a filter; project update doesn't touch statuses. Board columns in team-managed projects = statuses. Do column/status changes in the web UI (or drive Claude-in-Chrome), THEN use acli transition --status "<col name>" to move cards in — once a column exists, its name IS a valid transition target.
- Approving a user's access/join request (e.g. "X needs to join Jira") — not in
acli admin (which only activate/deactivate/delete managed accounts). It's an access-control grant: leave it to the user via the email approve link or admin.atlassian.com. (Also a hard safety rule: don't grant access on someone's behalf.)
- Verify a user got access (read-only):
home.atlassian.com/o/<orgId>/people/search?query=<name> — they show in "People you work with" with a recent-activity timestamp once active.
Driving the team-managed board UI in Chrome (column ops) — what actually works
Board URL: bellaslainte.atlassian.net/jira/software/projects/KAN/boards/2.
- Rename a column: double-click the column header title → it becomes an input →
cmd+a, type new name, Return. (Single-click on an empty new column also opens rename; single-click on an established column instead surfaces a "…" kebab.)
- Create a column: click the + at the far right of the column row. The floating name input has flaky focus — the reliable sequence is: click +, then explicitly click the input field, type the name, then click the ✓ check (or
Return + click empty board area). Verify with a screenshot; if no column appears, retry (first attempt often no-ops). New columns always append at the far right.
- Reorder a column: the "…" kebab has Move column left / Move column right (clean, click-based — preferred). Drag-the-header also works (
left_click_drag header→target x) BUT throws a transient "Something went wrong" error and blanks the board — it actually persisted; just navigate to reload and the new order is there. The kebab is hard to summon on freshly-created empty columns, so drag-then-reload was what worked for new columns.
- KAN's status set after the 2026-06-30 build: Idea, Pilot, Beta, RC1, In Progress, In Review, Done (Pilot/Beta/RC1 are phase columns split out of the old "To Do"). Phase is also mirrored on every card as a
phase-pilot/phase-beta/phase-rc1 label so it survives once a card moves into In Progress.
GOTCHAS (all learned the hard way — trust these)
search defaults to only 30 results. Always pass --limit 100 (or --paginate) or you silently miss rows. Output is a JSON array; fields are nested under each item's .fields (.fields.summary, .fields.status.name, .fields.labels).
view takes the key positionally (acli jira workitem view KAN-7), NOT --key. --key errors with "unknown flag".
create-bulk caps at 50 issues per call. More → must have at most 50 items. Split into batches.
create-bulk --from-json rejects a description field → 400 request body is missing or invalid. JSON only reliably takes summary, projectKey, issueType, label, assignee.
create-bulk --from-csv accepts description but SILENTLY IGNORES the label column. The call reports success; labels just never attach. Apply labels afterwards via edit.
create-bulk does NOT print the created keys, and --json is not supported on it. To get keys after a bulk create, search the project and match cards back to source rows by a prefix you embedded in the summary (e.g. A1 — ...).
edit --labels APPENDS labels, it does not replace. To change a label set, --remove-labels "old" then (or in another call) --labels "new". Re-running --labels to "fix" a label just stacks them.
- Flag name differs between create and edit:
create uses --label (singular); edit uses --labels (plural). CSV bulk column is label.
transition --status must match an existing workflow status name exactly (KAN: Idea / In Progress / Done). Batch many keys in one --key "a,b,c" call, or target by --jql.
assign (like create-bulk's siblings) prompts for confirmation — pass -y/--yes or it cancels with "assign cancelled". Same for transition, edit, delete.
assign --assignee <accountId> SILENTLY UNASSIGNS (reports "N work items were assigned" but the field ends up empty — confirmed 2026-06-30, only the authenticated user's own id stuck). Always assign by EMAIL (--assignee user@domain), which works reliably. Verify with a search --fields assignee afterwards, never trust the success line.
Custom "People" field in a team-managed project (Collaborators)
acli can't create a project-scoped field for team-managed projects. Create it in the UI, then populate via REST:
- Open any work item → Details panel → "Edit fields" (slider icon, top-right of Details) → Create field → Field type People (leave "Restrict to a single user" OFF for multi-user) → name it → Create.
- Get its id:
GET /rest/api/3/field → filter by name. (KAN's "Collaborators" = customfield_10041, schema custom ...customfieldtypes:people, type array.)
- Populate / overwrite per issue via REST PUT (from an authenticated browser tab, return a Promise — no top-level await):
PUT /rest/api/3/issue/{KEY} body {"fields":{"customfield_10041":[{"accountId":"..."},...]}} → 204. Same endpoint sets reporter ({"fields":{"reporter":{"accountId":"..."}}}) and any other field.
- KAN ownership model applied 2026-06-30: assignee = first-listed human in the sheet Owner field; Collaborators = the follow-up humans; AI agents ("Claude Code"/"Codex") and "Group" excluded; C4 (only "Group") left unassigned; C1 kept Georgia's self-assignment rather than the rule's (Isaac first, not invited). David↔Michael split rule: GCP/Terraform/sec-ops/infra + MFA → Michael; features/big product/architecture decisions → David (D1, D6, G6 went to David; D3/D7/D9/G1 stayed Michael).
Notifications (email off)
David's standing preference (2026-06-30): no email notifications anywhere, moving the team to Slack instead.
- Project-wide (all users): Project settings → Notifications → Settings = 13 per-event rows (each → All Watchers/Current Assignee/Reporter). DELETE all of them (per-row "…" → Delete → confirm) to stop the project sending any email. Reversible via "Add notification". acli has no reach here.
- Per-user (David's own):
/jira/settings/personal/notifications → master toggle "Send me emails for work item activity" OFF disables ALL his email (incl. mentions); the Default-notifications Email column greys out. Can't change other users' personal toggles.
- The "Email notifications off until " banner is a separate FREE-PLAN daily rate-limit, not a setting.
- Slack alternative: project notifications page has a "Connect to Slack" (OAuth, needs the user) to route events to a channel instead of email.
Jira → Slack updates (Tier-1 @mention automation, in progress 2026-06-30)
Goal: post to #bella-jira (Slack channel C0BE6S9BD60) on new-ticket + status-change, @-mentioning the owner. Slack app that posts via MCP = "Claude" (U0AMKLGELHZ).
- Built: team-managed Automation flow "Slack #bella-jira: new tickets + status changes (@owner)" (rule id 019f1768-3dc8-7193-853b-8b57429de645) — trigger = "Multiple work item events" (Work item created + Work item transitioned); action = Slack "Send message". Saved DISABLED.
- BLOCKER (user's OAuth): the Automation "Send message" action needs its OWN connection ("Atlassian Automation → Slack Actions"), SEPARATE from the native "Jira Cloud" Slack app. Shows a "Connect" button = OAuth grant, user-only. Until connected, can't set channel/message.
- After connect: set Channel = #bella-jira; message payload uses smart-value inline conditionals for the @mention, e.g.
{{#if(equals(issue.assignee.accountId,"<acctId>"))}}<@SlackID>{{/}} chained for all 9 (mapping in [[reference_bella_slainte_team]]). Current status shows via {{issue.status.name}} (works for both created + transitioned). Then enable + test via a transition.
- Free plan caps automation ~100 runs/month, single-project. Fallback = native Slack app subscription (text-only, unlimited, no @ping).
- Smart-value @mention via inline
{{#if}} chain avoids building a 9-branch UI switch.
- CSV: write with
csv.QUOTE_ALL (summaries contain commas, >, parentheses). Keep descriptions single-line (join with |) — avoids any newline-in-CSV-field parser risk.
Recipe: build a Kanban board from a Google Sheet
Proven flow (used to populate KAN from a Sheet of 57 items across 8 areas):
- Read the sheet:
gws sheets +read --spreadsheet <ID> --range "A1:Z200" --format json (note: +read uses --spreadsheet/--range flags, NOT --params).
- Build a CSV (
build_board_csv.py in this skill): columns summary,projectKey,issueType,description,label. Put a stable source-id prefix in the summary (A1 — <item>) so you can match keys later. Map a category column → one label per row (slugified, no spaces). Fold the other columns (owner, phase, status, notes, blockers) into a single-line description. Build a sidecar meta.json mapping source-id → target status.
- Wipe placeholders:
acli jira workitem delete --key "KAN-1,KAN-2,KAN-3" --yes (new projects seed 3 sample cards).
- Create in batches of ≤50:
acli jira workitem create-bulk --from-csv batch.csv --yes.
- Recover keys:
acli jira workitem search --jql "project = KAN ORDER BY created ASC" --limit 100 --json, match each card to its source-id by the summary prefix.
- Set columns: group keys by target status, then one
transition --key "..." --status "<name>" --yes per status (skip the default column).
- Apply category labels: group keys by label, one
edit --key "..." --labels "<slug>" --yes per group (remember it appends — start from cards with no labels).
- Verify:
search ... --fields status,labels --json, tally by column and label, confirm total and that no card is unlabelled.
Status mapping used for KAN: sheet Open → Idea, In progress → In Progress, Closed → Done, Blocked → In Progress + a blocked label (the default board has no Blocked column).