Linear (linear-cli) — Quick Reference
Linear is driven by Finesssee linear-cli (Rust; binary linear-cli, installed to ~/.cargo/bin). This is a short reference for the non-obvious parts — for everything else use linear-cli <cmd> --help, linear-cli common, or linear-cli agent.
Auth: linear-cli auth oauth (browser) or LINEAR_API_KEY; check with linear-cli auth status. The full issue lifecycle is automated by the in-repo skills (/start, /finish, /full, /checkpoint, /next, /quality-review, /prd, /spec, /triage) — no install step.
⚠️ Gotchas that bite (read these)
-
Anchored (inline) comments are invisible to the obvious commands. A comment made by highlighting text in the issue description is stored on the description's
documentContent, NOT onissue.comments.linear-cli comments list <ID>andissues getreturn only standalone comments and will report "no comments" while reviewer corrections sit on the description. To read an issue with its full comment thread, use the digest:~/.claude/scripts/linear-context.sh PL-13 # markdown digest: desc + deps + standalone AND anchored commentsRaw form (what the digest does): resolve the issue's
documentContent.id, thencomments(filter:{documentContent:{id:{eq:<that id>}}})vialinear-cli api query. -
No dependency commands or flags. There is no
depscommand and nosearch --has-blockers/--blocked-by/--has-circular-deps. Get the graph as{nodes, edges}and filter withjq:~/.claude/scripts/linear-deps-graph.sh PL-13 # local graph (issue + neighbors) ~/.claude/scripts/linear-deps-graph.sh --team PL # whole-team graph (active issues)Per-issue relations also exist directly:
linear-cli relations list <ID>. -
issues createhas no--parentflag — to set the parent at create time, pass its UUID asparentIdin--dataJSON (verified on 0.3.26;--datacarriesdescriptiontoo). For follow-ups use the helper anyway: it links viarelations parentand verifies the link (a bare--datacreate doesn't), failing hard on an orphan:~/.claude/scripts/linear-create-child.sh <parent|-> <team> <state|-> <title> <body-file> -
Unassign =
linear-cli issues assign <ID>with the user omitted. -
Workflow states =
linear-cli statuses list -t <TEAM>(there is noteams states). -
Escape hatch. Anything the dedicated commands can't do:
linear-cli api query/api mutaterun raw GraphQL against the Linear API (this is why we use linear-cli — the previous CLI had no such hatch). -
Labels are typed, can be team-scoped, and
-lREPLACES.labels listandlabels createdefault to--type project— pass-t issuefor issue labels (a project label can't be attached to an issue; probe withlinear-cli labels list -t issue -o json).issues update -lsets the entire label set (no add/remove subcommand) — to add one label without clobbering the rest, use~/.claude/scripts/linear-add-label.sh <ID> <label>(read-merge-set + verified attach);issues list -l <name>filters by label name. Issue labels can be team-scoped: attaching one to an issue in another team fails with GraphQLlabelIds for incorrect team, andlabels createhas no--teamflag, so it provisions workspace-level labels only (team-scoped ones must be created in the Linear UI). Thespecifiedcertification label is deliberately workspace-level so it attaches across all teams (standards/issue-spec.md);scripts/linear-file-improvement.shkeeps a best-effort attach (exit 2 + WARN) for the day a conflicting team-scoped label appears. -
issues update --statecan report success without the state actually changing. Exit code 0 and the printed+ Updated issuemessage are not confirmation — a follow-upissues get --no-cachemay still show the old state, even after retries and even when passing the state's UUID directly instead of its name. If a state update doesn't seem to have taken effect after a--no-cachere-check, fall back to the raw mutation (gotcha #6) and trust its own response over the wrapped command:linear-cli api mutate 'mutation($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success issue { id identifier state { id name } } } }' --variable id=<issue-uuid> --variable stateId=<state-uuid>— its response includes the resultingissue.state, so you can confirm the change immediately without a separateget. -
comments listneeds-o json— its table output is empty, and the JSON is a nested envelope. The default table prints a header row and ZERO data rows for every issue (| Author | Created | Body | ID |, thenN comments), so the bare command reads as "no comments" on an issue that has several — and/startStep 4 sends you here precisely for full standalone bodies, which the digest truncates to 140 chars. Always pass-o json; the payload is{"comments":{"nodes":[{"body":…}]},"id","identifier","title"}:linear-cli comments list <ID> -o json | jq -r '.comments.nodes[].body'Not
.[].body— iterating the top level hits theid/titlestrings and errors withCannot index string with string. Passing multiple IDs returns an ARRAY of those objects; use-o ndjsonand keep the same per-line filter.
Command map
# Issues (alias: i)
linear-cli issues get <ID> [-o json] # single issue (state is {name}; --comments adds STANDALONE comments only)
linear-cli issues list --team <KEY> [--limit N] [--state X] [--assignee me] [-l <label>] [-o json]
linear-cli issues create "<title>" --team <KEY> [--state X] [-d -] # description via stdin with -d -
linear-cli issues update <ID> [--state X] [--assignee me|<user>] [--priority N] [-l <label>]... [--data -] # -l SETS the whole label set — to add, use linear-add-label.sh (gotcha #7)
linear-cli issues assign <ID> [<user>] # omit <user> to UNASSIGN
linear-cli issues comment <ID> --body - # add a comment (body via stdin)
# Comments / relations / search / statuses
linear-cli comments list <ID> -o json # STANDALONE only (gotcha #1); -o json is REQUIRED — see gotcha #9
linear-cli relations add <BLOCKER> <BLOCKED> -r blocks # "A blocked by B" = relations add B A -r blocks (the blocked-by enum is broken on 0.3.26); also -r related|duplicate
linear-cli relations parent <CHILD> <PARENT> # set parent after create (issues create has no --parent flag; or set parentId via --data)
linear-cli search issues "<query>" [--filter 'state.name=Backlog'] # workspace-wide; NO --team flag (use `issues list --team` to scope)
linear-cli statuses list -t <KEY>
linear-cli labels list -t issue|project [-o json] # -t is label TYPE, not team; defaults to project (gotcha #7)
linear-cli labels create "<name>" -t issue [-c <hex>] # create an ISSUE label; no --team flag (gotcha #7)
linear-cli labels delete <LABEL-UUID> -t issue -f # takes the label id (from labels list -o json), not the name; -f skips the confirm prompt
# Projects / users / uploads
linear-cli projects get|list|create ...
linear-cli users list ; linear-cli whoami
linear-cli uploads fetch "<uploads.linear.app URL>" -f <file> # authenticated download
Output flags (agent-friendly): -o json|ndjson, -q (quiet), --id-only, --compact, --fields <a,b>.
In-repo helper scripts
| Script | Purpose |
|---|---|
| linear-context.sh <ID> | Full issue digest including anchored comments (gotcha #1). |
| linear-deps-graph.sh <ID> \| --team <KEY> | Dependency graph as {nodes, edges} (gotcha #2). |
| linear-create-child.sh <parent\|-> <team> <state\|-> <title> <body-file> | Parent-linked issue create — create → relations parent → verify (gotcha #3). |
| linear-post.sh <comment\|description> <ID> <body-file> | Post a comment or replace a description from a file. |
| linear-add-label.sh <ID> <label> | Add one issue label without clobbering the rest (read-merge-set + verified attach; gotcha #7). Exit 2 + create-label pointer when missing/unattachable. |
| linear-remove-label.sh <ID> <label> | Remove one issue label, preserving the rest (read-filter-set + verified; raw issueUpdate labelIds: [] for the last-label case). Exit 0 on already-absent. |
| mark-ready-for-release.sh <ID> | Move to Ready-For-Release and unassign. |