linear
Read and write Linear issues, comments, projects, cycles, users, documents, initiatives, milestones, and more via the Linear MCP server. Attach chat-uploaded screenshots to issues.
git clone --depth 1 https://github.com/Open-Curiosity/gini-agent /tmp/linear && cp -r /tmp/linear/skills/integrations/linear ~/.claude/skills/linearSKILL.md
# Linear
Linear is reachable through the `mcp_call` tool. Each call hits the hosted Linear MCP server; the runtime resolves the Authorization header from the connected Linear token. You never see the token or the endpoint — you pass `server: "linear"` and a tool name.
Every Linear call has the same shape:
```
mcp_call({
server: "linear",
tool: "<tool name>",
arguments: { ... }
})
```
The response is a JSON string. Parse it before reporting back to the user — Linear sometimes returns nested `pageInfo` or arrays you should summarize, not dump.
## Discovering what's available
**The full inventory of Linear MCP tools is in your system prompt** under "Configured MCP servers — linear — tools: ...". That list is authoritative; do not assume a tool doesn't exist just because it isn't called out below. The categories below give you defaults and taste; reach for tools from the inventory list whenever the user asks for something outside them.
When you're unsure of a tool's argument shape, either:
- Call `mcp_call({ server: "linear", tool: "search_documentation", arguments: { query: "..." } })` to query Linear's own docs, or
- Just try the call — the server returns a clear validation error on bad args, which is recoverable.
**Do not refuse a Linear-related ask** unless you've actually checked. "Linear doesn't support that" is a claim that requires evidence.
## Defaults and common shapes
### Issues — list / read / save
`save_issue` handles both create and update — with an `id` it updates, without one it creates.
```
mcp_call({ server: "linear", tool: "list_issues",
arguments: { assignee: "me", state: "started" } })
mcp_call({ server: "linear", tool: "get_issue",
arguments: { id: "LIN-123" } })
mcp_call({ server: "linear", tool: "save_issue",
arguments: { team: "ENG", title: "Login fails on Safari 17",
description: "Steps to reproduce…" } })
```
Useful argument quirks Linear's tool descriptions sometimes leave terse:
- `assignee: "me"` resolves to the authenticated viewer — no user-id lookup needed.
- `team` accepts the team key (`ENG`) or UUID.
- `state` accepts either the workflow state id or its display name (`"In Progress"`, `"Done"`). Workflow state *groups* for filtering: `backlog`, `unstarted`, `started`, `completed`, `cancelled`.
- `priority` is `0|1|2|3|4` where `1` is urgent, `4` is low, `0` is no priority.
- `list_issues` truncates `description` for compactness. Follow up with `get_issue` when the user needs the full body.
- Paginate by passing the `cursor` from `pageInfo.endCursor` back into the same call.
### Attaching screenshots / images
When the user provides images in chat and asks you to file or update a Linear issue, attach them after you know the issue identifier. The flow is three steps; the middle step (PUT bytes to Linear's signed URL) lives in the **`attachments` skill** — `read_skill name='attachments'` for the full reference. Linear-specific args are documented here.
1. **Prepare (Linear-specific)** — `mcp_call({ server: "linear", tool: "prepare_attachment_upload", arguments: { issue, filename, contentType, size } })`. The response carries `uploadRequest.url`, `uploadRequest.headers`, and `assetUrl`. The signed URL expires in 60 seconds; move immediately.
2. **PUT the bytes (attachments skill)** — `skill_run({ skill: "attachments", script: "signed-upload", args: { uploadId, url: prep.uploadRequest.url, headers: prep.uploadRequest.headers } })`. The script reads the upload off disk and PUTs it; the model never has to touch raw bytes. Returns `{ ok, status, bytesSent }`.
3. **Finalize (Linear-specific)** — `mcp_call({ server: "linear", tool: "create_attachment_from_upload", arguments: { issue, assetUrl, title, subtitle } })` with the `assetUrl` from step 1 and an optional `title` / `subtitle`.
Each user message that carries attachments ends with a system note listing each upload's id, mime type, and size:
```
Attached image uploads (in order):
- abc-123-... (image/png, 36116 bytes)
- def-456-... (image/jpeg, 24512 bytes)
```
Read the size and mimeType from that marker — `prepare_attachment_upload` rejects requests where `size` doesn't match the actual bytes (GCS returns `EntityTooLarge` / `EntityTooSmall`). One 3-step sequence per image.
If the `skill_run` step returns `ok: false`, the bytes did not land. Don't run the finalize step; either re-prepare (the signed URL is dead after 60s) or tell the user the upload failed and offer to paste the `assetUrl` into the issue body manually.
### Reading attachments on existing issues
To inspect or describe an attachment that's already on a Linear issue: combine `mcp_call(get_attachment)` with the **attachments skill's `signed-download` script** to land the bytes as a Gini upload, then `vision_query` to ask the model about the image. The attachments skill documents the full pattern — `read_skill name='attachments'`.
### Everything else (projects, cycles, comments, documents, initiatives, milestones, status updates, labels, diffs, users, teams)
Use the matching tool from the inventory list in your system prompt. The shapes are predictable: `list_*` returns an array with `pageInfo`, `get_*` takes an `id`, `save_*` creates without `id` / updates with `id`, `delete_*` takes an `id`. Examples:
```
mcp_call({ server: "linear", tool: "list_teams", arguments: {} })
mcp_call({ server: "linear", tool: "list_projects", arguments: { team: "ENG" } })
mcp_call({ server: "linear", tool: "list_cycles",
arguments: { team: "ENG", type: "current" } }) // also "next", "previous"
mcp_call({ server: "linear", tool: "list_comments",
arguments: { issueId: "LIN-123" } })
mcp_call({ server: "linear", tool: "save_comment",
arguments: { issueId: "LIN-123", body: "Reproduced on macOS 14." } })
mcp_call({ server: "linear", tool: "list_issue_labels", arguments: { team: "ENG" } })
mcp_call({ server: "linear", tool: "list_issue_statuses", argumentHow to dogfood and verify a Gini behavior change by driving a real chat turn as a real user would. Use when verifying that the agent reaches for a tool or path on its own — a behavioral steer, a new tool, an INSTRUCTIONS.md change, or a dispatch/provider/memory/skill change — or before claiming a steer "works". Enforces bare, uncoached prompts so the test measures the default, not instruction-following.
Delegate coding work to Claude Code CLI for repository edits, reviews, and multi-turn implementation sessions.
Delegate coding work to the OpenAI Codex CLI for repository changes, reviews, and focused fixes.
Gini's self-knowledge: how Gini configures, extends, and operates on its own state via /api/* and registered tools. Load when the user asks Gini about its own capabilities or asks Gini to modify its own configuration.
Manage Apple Notes via memo CLI: create, search, edit.
Apple Reminders via remindctl: add, list, complete.
Move bytes between Gini upload space, external URLs, and workspace files. Used by every attachment / file-upload / file-download flow regardless of the target system (Linear, GitHub, S3, Notion, etc.).
File a locally-captured, already-redacted Gini crash report as a GitHub issue, with the user's consent. Reads the pending crash queue and delegates the actual filing to the github-issues skill.