# Linear

> Use for ANY Linear interaction: read, view, list, search, create, edit, update, comment on, label, block, unblock, or activate any Linear issue, project, cycle, milestone, initiative, or label. Bash CLI over Linear's GraphQL API with local cache and mutation syncing.

- Skill: `zenobi-us/linear` (Agent Skill, multi-file: 53 files)
- Install (CLI): `npx skillmds@latest add zenobi-us/linear`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zenobi-us/linear/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: zenobi-us (https://skillmd.com/u/zenobi-us)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zenobi-us/linear

---


> **Never edit this file directly.** To make additions or modifications, edit the appropriate section in `./vstack.toml`. Then run `vstack refresh`.

# Linear CLI

CLI wrapper for Linear's GraphQL API with local cache, bulk operations, and structured output.

```bash
.agents/skills/linear/scripts/linear.sh <resource> <action> [options]
```

## Resources

### ctx7 CLI

| Library | ctx7 ID | Use For |
|---------|---------|---------|
| Linear API | `/websites/studio_apollographql_public_linear-api_variant_current` | GraphQL schema reference |
| Linear SDK | `/linear/linear` | SDK docs with examples |
| Linear Guides | `/websites/linear_app_developers` | Developer guides |

## Workflow Patterns

| Pattern | Use For |
|---------|---------|
| [patterns/workflow-actions.md](patterns/workflow-actions.md) | Multi-step issue/project state changes used by orch and TPM workflows |

## Commands

| Resource | Actions |
|----------|---------|
| `issues` | list, get, create, update, children, list-relations, add-relation, remove-relation, bulk-get, bulk-update, activate (`--agent <name>` applies the exclusive `agent:<name>` label), block, unblock, complete (`--summary <text>` / `--summary-file <path>` post the completion comment before the Done transition), validate-completion |
| `comments` | list, create (`--body` or `--body-file`) |
| `projects` | list, get, create, update, list-dependencies, add-dependency, remove-dependency, post-update, list-updates |
| `initiatives` | list, get, create, add-project |
| `milestones` | list, get, create |
| `labels` | list, create |
| `project-labels` | list, create |
| `teams` | list, get |
| `users` | list, get |
| `cycles` | list |
| `statuses` | list, get |
| `documents` | list, get |
| `sync` | Sync Linear data to local cache |
| `cache` | Query local cache (issues, projects, cycles, initiatives, comments, labels, attachments) |
| `auth-check` | Validate API key |

Compatibility aliases: `issues relations` maps to `issues list-relations`, and `projects dependencies` maps to `projects list-dependencies`. Prefer the explicit action names in new workflows.

There is no `view` or `show` action. Single-issue lookups are `issues get <ID>` (live) or `cache issues get <ID>` (cache); multi-issue lookups are `issues bulk-get <ID1> <ID2> ...`. For post-mutation verification, use live `issues bulk-get` — it returns fresh state for every mutated issue in one command.

## Hierarchy

```
INITIATIVE (Strategic goal — months)
  └── PROJECT (2-6 week deliverable)
        ├── MILESTONE (stage: Alpha, Beta, Release)
        │     └── ISSUE (1-5 day work item)
        └── ISSUE (work item without milestone)
              └── SUB-ISSUE (breakdown for parallel work)
```

## Cache Pattern

Reads go through `cache`. Writes go through live commands (auto-update cache via write-through). Sync at session start or when cache is stale.

```bash
# READS → cache (fast, no API calls)
linear.sh cache issues list --project "Phase 2" --state "Todo,In Progress"
linear.sh cache issues list --all-projects --state "Backlog,Todo" --max --format=compact
linear.sh cache issues get ABC-100 --with-bundle

# WRITES → live (hit API, auto-update cache)
linear.sh issues create --title "New task" --project "Phase 2"
linear.sh issues update ABC-100 --state "Done"

# SYNC → refresh cache
linear.sh sync --reconcile      # Incremental + reconcile archived
linear.sh sync --full           # Full re-sync
```

`cache issues list --all-projects` enumerates every project in ONE command — each row carries its `project` name, and other filters (`--state`, `--max`, `--format`) compose. Use it for cross-project comparison sets instead of looping `--project` per project; restricted harness approval policies reject loop-shaped commands. Mutually exclusive with `--project`.

Cache and attachment files live under `.cache/linear` in the physical git worktree root reported by `git rev-parse --show-toplevel`, not under the path used to reach the skill script. This keeps `sync`, `cache`, and attachment reads consistent across symlinked checkout spellings, worktrees, and canonical source-path invocation. A missing-cache error includes the checked `cache_dir` and `meta_path`; inspect those fields before assuming a sync wrote somewhere else.

## Output Formats

| Format | Description |
|--------|-------------|
| `safe` | DEFAULT. Flat, null-safe JSON |
| `ids` | Newline-separated identifiers |
| `table` | Human-readable table |
| `raw` | Original GraphQL structure |

`compact` omits description and other large text fields. `raw` nests fields under GraphQL structure — do not assume top-level jq paths. Use `safe` (default) when you need issue descriptions or full field access.

## Configuration

| Variable | Purpose | Default |
|----------|---------|---------|
| `LINEAR_API_KEY` | API key (required for live API commands and sync; not required for cache reads) | — |
| `LINEAR_TEAM` | Default team name | `Claude` |
| `LINEAR_FORMAT` | Default output format | `safe` |
| `LINEAR_TEAM_PREFIX` | Issue identifier prefix | `CC` |

Put `LINEAR_API_KEY` in `.env.local`. Put non-secret defaults in committed `vstack.settings.toml` under `[env]`; `.env.local` still wins for local overrides.

## Safe Format Field Mapping

```
identifier → id         # ABC-XXX issue ID
id → uuid              # GraphQL UUID
state.name → state     # State name
state.type → state_type
sortOrder → sort_order  # Manual sort position
```

## Blocked Label vs Issue Relations

| Scenario | Use |
|----------|-----|
| Issue A blocked by Issue B (both in Linear) | Relation: `--blocked-by` |
| Issue blocked by external factor (vendor, license) | `blocked` label + comment |

Blocking relations must connect peers of one bundle: same direct parent, or both top-level (and same project). An issue cannot block its own ancestor or descendant — the parent-child hierarchy already encodes that dependency; use `--related` for traceability. Rejections for cross-subtree pairs prescribe the valid pair at the level where the subtrees separate. Before either acceptance or remediation, the guard proves each parent chain reaches an explicit null root through well-formed edges with unique IDs/identifiers. It also requires an explicit null or well-formed project value; incomplete, cyclic, or malformed hierarchy data is rejected before mutation.

A blocking relation pointing at a Done or Canceled issue is **satisfied history, not stale metadata** — Linear itself already treats the dependent issue as unblocked. The relation stays for provenance and traceability; never remove or "fix" it, and audits must never classify it as stale. The only legitimate audit output for a completed-blocker relation is a scheduling signal ("gates cleared, ready to schedule"). Cautionary precedent: an audit agent once emitted a "STALE blocked_by METADATA" section listing issues whose blockers were Done, framing valid history as defects and inviting destructive cleanup — the issues were simply ready to schedule.

## Common Pitfalls

| Option | Accepts | On failure |
|--------|---------|-----------|
| `--project` | Name or UUID | Fail with "not found" |
| `--state` | Exact name (case-sensitive) | Fail, lists available states |
| `--milestone` | Name or UUID | Fail with "not found" |
| `--parent` | Issue identifier or UUID | Fail if the parent cannot be resolved; create also fails if the link cannot be verified or repaired |
| `--labels` | Comma-separated issue-label names | Warn + skip invalid, continue (workflow callers must preflight strictly) |
| `--assignee` | Name or `me` | Silent fail |

- State names are case-sensitive and team-specific — verify with `linear.sh statuses list`
- Available states: Backlog, Todo, In Progress, In Review, Done, Canceled (not "Cancelled")
- `agent:*` labels are mutually exclusive (only one per issue)
- `issues activate ISSUE --agent NAME` applies `agent:NAME` in the same update as the "In Progress" transition, replacing any existing `agent:*` label; it fails without changing state when the label does not exist
- `issues complete ISSUE --summary-file PATH` posts the completion summary comment first and only then transitions to "Done"; a failed post leaves the state unchanged
- `issues validate-completion` is a pre-merge check: session-root targets are expected in "In Progress"/"In Review" (Done fails `state_ok` — managed roots stay pre-merge until PR merge; this pre-merge rule applies to the session root only). `--include-children-of` expands the bundle and validates each child as "Done": every completed child IS included and passes, a still-pending child fails, and canceled children are excluded from the expansion (abandoned work is never "Done")
- `--labels` replaces the full issue-label set on update. Workflow callers must fetch current labels, compute the final set, validate against `cache labels list --format=safe`, then call update with the full final set.
- `cache labels list --format=safe` returns issue labels with `id`, `name`, `team`, `parent`, and `is_group` so workflows can reject parent/group labels before mutation.
- `issues bulk-update` is non-atomic. If one item fails after earlier updates succeeded, it emits a JSON summary with `partial: true`, per-issue results, and exits nonzero.

## Troubleshooting

- **"labelIds not exclusive child labels" error**: Using multiple labels from the same exclusive group. Only one `agent:*` label and one `platform:*` label per issue.
- **Need raw GraphQL output?**: Use `--format=raw`
- **Script help**: `linear.sh <resource> --help`

## Dependencies

- Bash 4.0 or newer (macOS system Bash 3.2 is unsupported; install a newer Bash and invoke `linear.sh` with it)
- `curl`
- `jq`

