Files
magnus919_agent-skills/linear/references/graphql-contract.md
T
Magnus HedemarkGitHubfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
b8a5092c26 feat(linear): project mutations and richer issue verbs in CLI (#288)
## What this adds

Implements the request in #287 and the Tier 1 audit gaps for the `linear` skill's `scripts/linear` CLI, reconciled against the live Linear GraphQL schema.

### New verbs
- `linear project update` — name, description, status, start/target dates, priority, with the same `--dry-run`/`--confirm` gate as issue mutations, and a local 255-character description guard matching Linear's `projectUpdate` limit (Linear rejects longer descriptions with a generic error).
- `linear issue archive` / `linear issue unarchive` — both gated, returning `IssueArchivePayload.entity`.
- `linear state list --team ENG` — first-class workflow-state discovery (previously states were only visible in the `issue move` failure path).

### Richer issue verbs
- `issue create` now accepts `--project`, `--parent`, `--assignee`, `--label` (repeatable), `--state`, `--due`.
- `issue update` now accepts `--assignee`, `--label` (add), `--remove-label`, `--due`, `--project`.

### Resolution rules (all require exactly one match, mirroring `resolve_team`)
- Project: UUID or exact name
- Parent: issue identifier or UUID
- Assignee: exact name, display name, or email (via `users`)
- Label: exact name within the issue's team (via `team.labels`)
- Workflow state: exact name within the issue's team (existing `team.states` resolver, now reusable for `--state` on create)
- Project status: exact name or type (via `projectStatuses`)

### Docs, tests, evals
- SKILL.md command map, state-change gate, and error/recovery sections; README; `domain-and-workflows.md` (project semantics + 255-char limit), `graphql-contract.md` (resolution queries), `integration-boundaries.md` (intentional exclusions list), `sources.md` (2026-08-05 schema re-verification note).
- 15 new offline tests (45 total) covering resolution, gates, dry-run intent, payload shapes, and field guards.
- Added a sixth eval case (`safe-project-and-issue-mutations`).

## Validation
- `python3 -m unittest linear/tests/test_linear.py` — 45/45 pass
- `python3 scripts/validate-evals.py`, `ruby scripts/validate-skills.rb`, `python3 scripts/check-artifacts.py`, `python3 scripts/eval-coverage.py --modified-from origin/main`, skill-quality validator, marketplace/codex/llms freshness, jscpd — all green locally

Closes #287

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-05 16:43:36 -04:00

3.1 KiB

GraphQL Contract

Endpoint: https://api.linear.app/graphql. Send JSON GraphQL requests with Content-Type: application/json. A personal API key is the value of the Authorization header. An OAuth access token is sent in the Authorization header with the Bearer prefix.

This CLI supports bounded reads for teams, issues, documents, projects, cycles, and workflow states, plus issue create, update, move, archive, unarchive, and comment mutations, and project update. Use raw for a documented operation outside that surface. Issue reads accept a UUID or shorthand identifier. Document reads accept a UUID, slug ID, or Linear document URL; slug lookup uses the documents filter.

Friendly-name resolution uses bounded queries: teams via the teams connection, users by exact name/email via the users connection, labels within a team via team.labels, project statuses via projectStatuses, and workflow states within a team via team.states. Each resolver requires exactly one match and fails with the available names on ambiguity. Resolvers query at most 100 items; a workspace or team with more matches than the first page cannot be resolved by name.

For personal scripts, a personal API key is the simplest authentication method. OAuth is intended for applications acting on behalf of users; access tokens are sent with the Bearer prefix. This CLI does not initiate OAuth, store credentials, refresh tokens, or print either supported credential.

Filter input supports equality, inequality, collection membership, comparisons for number/date fields, and string operators such as contains and startsWith. Relationship filters can narrow results by related team, state, assignee, project, or labels. Prefer these filters to client-side filtering and ask for only fields needed for the task.

All connections use Relay cursor pagination: request first and then pass pageInfo.endCursor as after while hasNextPage is true. The CLI intentionally does not automatically paginate; keep reads bounded with --limit. Use server filters rather than downloading a workspace and filtering locally.

Check GraphQL's errors array even on HTTP 200 because data can be partial. Rate limits and query complexity are reported in response headers. As accessed on 2026-07-17, the official rate-limit page contains a conflicting API-key request limit (5,000 in prose and 2,500 in its table); inspect current headers and documentation rather than hard-coding either value. It also documents API-key complexity at 3,000,000 points per hour and a 10,000-point maximum for one query.

Linear does not version this GraphQL API. Inspect schema deprecations and the [API] changelog before relying on a field. When a query fails after a schema change, re-run public introspection in Apollo Studio, update the narrow field selection, and add an offline contract test before release.

Archived records are excluded from paginated responses by default and can be included with includeArchived: true when the connection supports it. Do not add polling loops for updates: Linear recommends webhooks for applications that need near-real-time changes.