Agent-first interface to Linear.app via the linear-cli Rust binary. Prefer this over Linear MCP tooling — linear-cli commands cost roughly 50–100 tokens per call versus 500–2000 for an MCP round-trip, support --output json everywhere, and cover the full Linear surface (issues, projects, cycles, sprints, webhooks, raw GraphQL).
When To Use
Trigger on any of these tells:
- user pastes a
LIN-123(or any[A-Z]+-\d+) identifier or ahttps://linear.app/...URL - user invokes
linear-cli,lc, or anyi create / i list / i update / b assign / b update-state / im csv / s issuessubcommand - user wants to create many Linear issues from a spec, checklist, CSV, JSON, or template — "create N issues", "import this list", "spawn issues from this TODO"
- user wants to start work on an issue and ship a PR, or close the issue tied to the current branch — "start LIN-123", "open a PR for this issue", "mark done"
- user asks to bulk-mutate Linear issues — assign, label, transfer, status-change, archive across many IDs
- user wants to search the Linear backlog, apply a saved view, or filter for an agent — "find stale issues", "what's in triage", "list my open ENG tickets"
- user is wiring webhooks, watch-polling, attachments, or raw GraphQL against Linear
- user hits an auth, rate-limit, or pager problem with
linear-cliand needs the recovery move
Do NOT use when:
- the user explicitly mandates the Linear MCP server — flag the cost gap (50–100 vs 500–2000 tokens) once, then comply
- the work is GitHub Issues, not Linear — route to
run-issue-tree,gh issue, ordo-review - the task is webhook server / payload-contract design where
linear-cliis incidental — payload schema dominates - the user is asking a math or geometry question about "linear" anything — not a Linear.app task
Non-negotiable rules
- CLI > MCP. Never call a Linear MCP tool when
linear-clican do the same thing. - Validate auth before any mutating command — run
linear-cli auth status --validate --output jsonorLINEAR_API_KEY=... linear-cli u me --output jsonand only proceed on exit code 0. Confirm the intended workspace/profile before bulk writes. --dry-runfirst for any bulk create or any mutation across more than 5 IDs. Confirm the plan, then execute.- Always
--output json(orLINEAR_CLI_OUTPUT=json) when chaining or parsing. Never grep the human table format. --id-onlyfor chaining. Capture IDs into shell variables; do not re-parse JSON for an identifier you already produced.- Treat the exit-code map as a contract:
0success,1general error,2not found or parser error,3auth,4rate-limited (retry_afteris in the JSON body). - Resolve names before using them. Do not invent unknown team keys, label names, or assignees. Pull them with
linear-cli t list,linear-cli l list,linear-cli u listand confirm before write paths. - Be explicit about scope. State whether each command is read-only or mutating. Bulk mutations get a one-line confirmation cue.
Exit codes (contract)
| Code | Meaning | Agent action |
|---|---|---|
| 0 | success | continue |
| 1 | general error | parse stderr / JSON error envelope; surface to user |
| 2 | not found or parser error | If JSON envelope with details.status: 404, the ID doesn't exist. If plain text stderr (e.g. unexpected argument), it's a CLI argument-parsing failure — check --help and fix the syntax |
| 3 | auth error | route to references/setup.md and references/troubleshooting.md |
| 4 | rate limited | sleep retry_after seconds (in JSON body), retry once |
JSON error envelope: {"error": true, "message": "...", "code": N, "details": {...}, "retry_after": N} — see references/json-shapes.md.
Parser vs. API distinction: Parser errors are plain text (e.g. Usage: ..., unexpected argument). Linear API errors are JSON envelopes with a code field and details.status HTTP status. Treat parser errors as mistakes to fix immediately, not transient failures to retry.
Quick orientation
| Concept | What an agent must know |
|---|---|
| Aliases | i issues · p projects · c cycles · g git · s search · b bulk · cm comments · tpl templates · l labels · st statuses · tr triage · sp sprint · att attachments · up uploads · rel relations · n notifications · ms milestones · pu project-updates · t teams · u users · wh webhooks · mt metrics · tm time · hist history · fav favorites · v views · rm roadmaps · init initiatives · d documents · im import · exp export |
| JSON-everywhere | Add --output json (or --output ndjson) to anything. Combine with --compact --fields a,b.c to slim payloads. |
| Session default | export LINEAR_CLI_OUTPUT=json makes the whole shell JSON-by-default — flip it on at the top of an agent session. |
| Stdin | -d - reads description body from stdin; --data - reads a full JSON object for i create/i update. |
| Pagination | --limit N, --all, --page-size N, --after CURSOR. |
| Self-help | linear-cli agent prints an agent-focused capability summary; linear-cli --help and linear-cli <cmd> --help for everything. |
Agent-critical flags (cheat sheet)
| Flag | Use |
|---|---|
--output json / --output ndjson |
Machine-readable output. |
--compact |
Strip pretty-print whitespace. |
--fields a,b.c |
Whitelist fields (dot paths supported). |
--sort field / --order asc|desc |
Stable sort for JSON arrays. |
--filter k=v / k!=v / k~=v |
Client-side filter (case-insensitive, dot paths). |
--format tpl |
Template output, e.g. "{{identifier}} {{title}}". |
--id-only |
Print only the created/updated ID. |
--quiet / -q |
Suppress decoration. |
--fail-on-empty |
Non-zero exit when list is empty (great for set -e agents). |
--dry-run |
Preview without writing. |
--yes |
Auto-confirm prompts. |
--no-pager / --no-cache |
Disable auto-pager / read-through cache. |
-d - / --data - |
Read description body / full JSON object from stdin. |
--limit N / --all / --page-size N / --after CURSOR |
Pagination. |
Full matrix: references/output-and-scripting.md.
Agentic hot path
The 80% of Linear work an agent reaches for. Memorize; route the rest through references.
Read
linear-cli i list # all open issues
linear-cli i list --mine -t ENG # my open issues on team ENG
linear-cli i list -s "In Progress" --output json --compact --fields identifier,title,state.name
linear-cli i get LIN-123 # one issue
linear-cli i get LIN-1 LIN-2 LIN-3 --output json # batch fetch (one API call, not three)
linear-cli s issues "auth bug" --limit 10 # search
linear-cli context --output json # issue from current git branch
Create one issue
linear-cli i create "Fix login redirect" -t ENG -p 1 # priority 1=urgent..4=low
linear-cli i create "Task" -t ENG -a me -l bug --due +3d
linear-cli i create "Bug" -t ENG --id-only --quiet # capture ID
cat desc.md | linear-cli i create "Title" -t ENG -d - # body from stdin
cat issue.json | linear-cli i create "Title" -t ENG --data - # full JSON body
linear-cli i create "Test" -t ENG --dry-run # preview
Create many issues — primary use case
For workflows that produce a batch of issues from a spec, checklist, CSV, JSON, or template, route to references/recipes/creating-many-issues.md for the end-to-end recipes (atomicity, parent/child wiring, label batching, dry-run gating). Inline summary:
# Pattern A — capture IDs in a loop (markdown checklist → many issues)
mapfile -t TITLES < <(grep -E '^- \[ \] ' TODO.md | sed -E 's/^- \[ \] //')
for title in "${TITLES[@]}"; do
linear-cli i create "$title" -t ENG --dry-run
done
if [ "${#TITLES[@]}" -gt 5 ] && [ "${CONFIRMED_BULK_LINEAR:-}" != 1 ]; then
echo "Review the dry-run output, then rerun with CONFIRMED_BULK_LINEAR=1 to create ${#TITLES[@]} issues."
exit 0
fi
for title in "${TITLES[@]}"; do
ID=$(linear-cli i create "$title" -t ENG --id-only --quiet)
echo "$ID"
done
# Pattern B — CSV import (preview, confirm, then commit)
linear-cli im csv issues.csv -t ENG --dry-run
if [ "${CONFIRMED_BULK_LINEAR:-}" != 1 ]; then
echo "Review the dry-run output, then rerun with CONFIRMED_BULK_LINEAR=1 to import issues.csv."
exit 0
fi
linear-cli im csv issues.csv -t ENG
# Pattern C — JSON spec
TITLE=$(jq -r .title plan.json)
linear-cli i create "$TITLE" -t ENG --data - < plan.json
Update
linear-cli i update LIN-123 -s Done # status
linear-cli i update LIN-123 -p 2 -a me -l bug -l urgent
linear-cli i update LIN-123 --due +1w
linear-cli cm create LIN-123 -b "Root cause: missing null check"
Bulk mutate
# Small, explicit set
linear-cli b update-state Done -i LIN-1,LIN-2,LIN-3 --dry-run
linear-cli b update-state Done -i LIN-1,LIN-2,LIN-3
linear-cli b assign me -i LIN-1,LIN-2 --dry-run
linear-cli b assign me -i LIN-1,LIN-2
# Generated set: capture, inspect, dry-run, then execute the same IDs
linear-cli i list -t ENG -l stale --limit 25 --output json --compact \
--fields identifier,title,state.name > /tmp/linear-stale.json
jq -r '.[] | "\(.identifier)\t\(.state.name)\t\(.title)"' /tmp/linear-stale.json
COUNT=$(jq 'length' /tmp/linear-stale.json)
IDS=$(jq -r '.[].identifier' /tmp/linear-stale.json | paste -sd,)
[ "$COUNT" -gt 0 ] && [ "$COUNT" -le 25 ] || { echo "Unexpected count: $COUNT"; exit 1; }
linear-cli b assign me -i "$IDS" --dry-run
if [ "$COUNT" -gt 5 ] && [ "${CONFIRMED_BULK_LINEAR:-}" != 1 ]; then
echo "Review the dry-run output, then rerun with CONFIRMED_BULK_LINEAR=1 to execute: linear-cli b assign me -i \"$IDS\""
exit 0
fi
linear-cli b assign me -i "$IDS"
Git / PR loop
linear-cli i start LIN-123 --checkout # assigns me + In Progress + creates branch
# ... code ...
linear-cli g pr LIN-123 --draft # gh-backed; auto-fills title/body from issue
linear-cli done # close the current branch's issue
Branch convention: <user>/lin-123-<slug>. linear-cli context reverses branch → issue.
Triage
linear-cli tr list -t ENG # unassigned, no-project queue
linear-cli tr claim LIN-123 # assign-to-self + move out of triage
linear-cli tr snooze LIN-123 --duration 1w
Use-case decision routing
| What the user wants | Read |
|---|---|
| "I have a spec / checklist / CSV / JSON; create N issues" | references/recipes/creating-many-issues.md |
| "Start work on an issue and ship a PR" | references/recipes/git-and-pr-loop.md |
| "Process the inbox / leave findings / fetch a screenshot" | references/recipes/triage-and-comments.md |
| Full create/update/move/transfer/archive flag matrix | references/issues/lifecycle.md |
| Parent/child, blocks, duplicates, labels, statuses, templates | references/issues/relations-and-labels.md |
i list flag matrix, --filter, saved views, favorites |
references/issues/search-and-filter.md |
| Projects, project-updates, milestones, cycles, sprints, roadmaps, initiatives | references/planning/projects-and-cycles.md |
| Teams, users, custom views, favorites | references/planning/teams-and-org.md |
| CSV / JSON / Markdown round-trip import-export | references/data/import-export.md |
| Attachments, URL linking, downloading uploaded images for multimodal review | references/data/attachments-and-uploads.md |
| Watch polling, webhooks (CRUD + HMAC listener), notifications, metrics, history, time tracking | references/eventing-and-tracking.md |
| Raw GraphQL escape hatch, Linear documents | references/advanced.md |
| First-time auth, OAuth + PKCE, workspace switching, doctor, completions | references/setup.md |
| All output flags, exit codes, env vars, stdin patterns, pagination, chaining recipes | references/output-and-scripting.md |
| Concrete JSON payload shapes for issues / comments / context / errors | references/json-shapes.md |
| Auth failure, rate limit, broken pager, stale cache, missing command | references/troubleshooting.md |
Output contract
When answering a Linear question, return:
- Scope line — read-only, mutating, or destructive.
- Exact command(s) — copy-pasteable, with
--output jsonif the result will be parsed. - JSON suggestion — show the agent-friendly variant beside the human one when both are useful.
- Exit-code interpretation — only when relevant (auth, not-found, rate-limit).
- Near-neighbor distinction — call it out when commands are easy to confuse (
b update-statevsi update,i startvsg checkout,tpllocal vstpl remote-*,cmissue comments vspuproject updates). - Dry-run note — required for any bulk create or any mutation across >5 IDs.
Guardrails
- Do not call Linear MCP tools when
linear-cliis installed. - Do not run
b update-state,b assign,b label, orim csvagainst more than 5 IDs without--dry-runfirst. - Do not pipe through pagers in CI or agent runs — set
LINEAR_CLI_NO_PAGER=1or pass--no-pager. - Do not embed OAuth flows in agent scripts; prefer
LINEAR_API_KEYfrom the environment. - Do not store API keys in the repo or in shell history. Pipe them in:
printf '%s\n' "$LINEAR_API_KEY" | linear-cli config set-key. - Do not fabricate team keys, label names, or user names. Resolve them with
t list/l list/u listfirst. - Do not parse human-formatted tables. Always
--output jsonfor chained or programmatic use. - Do not assume a command exists because the docs mention it; if missing, run
linear-cli updateto refresh the binary, then retry. - Do not chain destructive commands without confirming the IDs match what was previewed in
--dry-run.
Recovery moves
- Auth fails (exit 3):
linear-cli auth status --validate --output json→ re-runauth loginorauth oauth. Seereferences/setup.md. - Rate-limited (exit 4): read
retry_afterfrom the JSON envelope, sleep that many seconds, retry once. Seereferences/troubleshooting.md. - Bulk mutation hit a partial failure: capture exit code per item with a JSON loop; rollback by inverting the mutation (e.g.
b update-state "In Progress" -i LIN-1,LIN-2to undo). Seereferences/recipes/creating-many-issues.md. - Pager left terminal in raw mode (macOS):
resetorstty sane; rerun with--no-pager. Seereferences/troubleshooting.md. - Command in upstream docs but not in your binary:
linear-cli update(orlinear-cli update --checkfirst). Seereferences/troubleshooting.md.
Final checks before declaring done
- auth validated (
auth status --validate --output jsonoru me --output json) before mutations - intended workspace/profile confirmed before bulk mutations
-
--dry-runpreviewed for any bulk operation > 5 IDs - every chained command used
--output jsonand--id-onlywhere appropriate - exit code interpreted (especially 2/3/4)
- team keys, label names, assignees resolved from list commands, not hardcoded
- no Linear MCP fallback used when
linear-cliwas available