Linear GraphQL

SkillFiles & storage

Linear GraphQL patterns for Symphony agents. Use `linear_graphql` for all operations — comments, state transitions, PR attachments, file uploads, and issue creation. Never use schema introspection.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Linear GraphQL skill

What this skill tells your AI

The instructions your AI receives, as published by hamedmp/matrix-os in .agents/skills/linear/SKILL.md and read by ahel’s review.

All Linear operations go through the linear_graphql client tool exposed by Symphony's app server. It handles auth automatically.

{
  "query": "query or mutation document",
  "variables": { "optional": "graphql variables" }
}

One operation per tool call. A top-level errors array means the operation failed even if the tool call completed.

Workpad

Maintain a local workpad.md in your workspace. Edit freely (zero API cost), then sync to Linear at milestones — plan finalized, implementation done, validation complete. Do not sync after every small change.

First sync — create the comment, save the ID:

mutation CreateComment($issueId: String!, $body: String!) {
  commentCreate(input: { issueId: $issueId, body: $body }) {
    success
    comment { id }
  }
}

Write the returned comment.id to .workpad-id so subsequent syncs can update.

Subsequent syncs — read .workpad-id, update in place:

mutation UpdateComment($id: String!, $body: String!) {
  commentUpdate(id: $id, input: { body: $body }) { success }
}

Query an issue

The orchestrator injects issue context (identifier, title, description, state, labels, URL) into your prompt at startup. You usually do not need to re-read.

When you do, use the narrowest lookup for what you have:

# By ticket key (e.g. MT-686)
query($key: String!) {
  issue(id: $key) {
    id identifier title url description
    state { id name type }
    project { id name }
  }
}

For comments and attachments:

query($id: String!) {
  issue(id: $id) {
    comments(first: 50) { nodes { id body user { name } createdAt } }
    attachments(first: 20) { nodes { url title sourceType } }
  }
}

State transitions

Fetch team states first, then move with the exact stateId:

query($id: String!) {
  issue(id: $id) {
    team { states { nodes { id name } } }
  }
}
mutation($id: String!, $stateId: String!) {
  issueUpdate(id: $id, input: { stateId: $stateId }) {
    success
    issue { state { name } }
  }
}

Attach a PR or URL

# GitHub PR (preferred for PRs)
mutation($issueId: String!, $url: String!, $title: String) {
  attachmentLinkGitHubPR(issueId: $issueId, url: $url, title: $title, linkKind: links) {
    success
  }
}

# Plain URL
mutation($issueId: String!, $url: String!, $title: String) {
  attachmentLinkURL(issueId: $issueId, url: $url, title: $title) {
    success
  }
}

File upload

Three steps:

  1. Get upload URL:
mutation($filename: String!, $contentType: String!, $size: Int!) {
  fileUpload(filename: $filename, contentType: $contentType, size: $size, makePublic: true) {
    success
    uploadFile { uploadUrl assetUrl headers { key value } }
  }
}
  1. PUT file bytes to uploadUrl with the returned headers (use curl).
  2. Embed assetUrl in comments/workpad as ![description](url).

Issue creation

Resolve project slug to IDs first:

query($slug: String!) {
  projects(filter: { slugId: { eq: $slug } }) {
    nodes { id teams { nodes { id key states { nodes { id name } } } } }
  }
}

Then create:

mutation($input: IssueCreateInput!) {
  issueCreate(input: $input) {
    success
    issue { identifier url }
  }
}

$input fields: title, teamId, projectId, and optionally description, priority (0–4), stateId. For relations, follow up with:

mutation($input: IssueRelationCreateInput!) {
  issueRelationCreate(input: $input) { success }
}

Input: issueId, relatedIssueId, type (blocks or related).

Rules

  • No introspection. Never use __type or __schema queries. They return the entire Linear schema (~200K chars) and waste the context window. Every pattern you need is documented above.
  • Keep queries narrowly scoped — ask only for fields you need.
  • Sync the workpad at milestones, not after every change.
  • For state transitions, always fetch team states first — never hardcode state IDs.
  • Prefer attachmentLinkGitHubPR over generic URL attachment for GitHub PRs.

Signals

GitHub stars
64
Forks
20
Last commit
Sep 2026
Hacker News mentions
20

ahel review

  • S4info
    community integration — published by hamedmp, not linear

Automated review, not a security audit. Ruleset v1.

Advanced
Catalog kind
skill
Gateway key
linear-hamedmp
Source
github.com/hamedmp/matrix-os