# Linear — 问题与项目管理
通过 GraphQL API 直接使用 curl 管理 Linear 的问题、项目和团队。无需 MCP 服务器、无需 OAuth 流程、无需额外依赖。
https://api.linear.app/graphql(POST)Authorization: $LINEAR_API_KEY(API 密钥不需要 "Bearer" 前缀)Content-Type: application/jsonENG-123)都可用于 issue(id:)基础 curl 模式:
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ viewer { id name } }"}' | python3 -m json.tool
如果你想要更快的单行命令、不必手写 GraphQL,本技能附带一个位于 scripts/linear_api.py 的标准库 Python CLI。零依赖。认证方式相同(读取 LINEAR_API_KEY)。
SCRIPT=$(dirname "$(find ~/.hermes -path '*skills/productivity/linear/scripts/linear_api.py' 2>/dev/null | head -1)")/linear_api.py
python3 "$SCRIPT" whoami
python3 "$SCRIPT" list-teams
python3 "$SCRIPT" get-issue ENG-42
python3 "$SCRIPT" get-document 38359beef67c # fetch a doc by slugId from the URL
python3 "$SCRIPT" raw 'query { viewer { name } }'
所有子命令:whoami、list-teams、list-projects、list-states、list-issues、get-issue、search-issues、create-issue、update-issue、update-status、add-comment、list-documents、get-document、search-documents、raw。运行 --help 查看参数。
什么时候用脚本:你想要快速答案而不想手写 GraphQL。什么时候用 curl:你需要脚本未封装的查询,或想内联组合过滤器。
Linear 使用带 type 字段的 WorkflowState 对象。6 种状态类型:
| 类型 | 说明 |
|------|-------------|
| triage | 需要评审的入站问题 |
| backlog | 已确认但尚未规划 |
| unstarted | 已规划/就绪但未开始 |
| started | 正在积极处理 |
| completed | 已完成 |
| canceled | 不做了 |
每个团队都有自己的命名状态(例如「In Progress」的类型是 started)。要更改问题的状态,你需要目标状态的 stateId(UUID)——先查询工作流状态。
优先级值:0 = 无,1 = 紧急,2 = 高,3 = 中,4 = 低
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ viewer { id name email } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ teams { nodes { id name key } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ workflowStates(filter: { team: { key: { eq: "ENG" } } }) { nodes { id name type } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issues(first: 20) { nodes { identifier title priority state { name type } assignee { name } team { key } url } pageInfo { hasNextPage endCursor } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ viewer { assignedIssues(first: 25) { nodes { identifier title state { name type } priority url } } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issue(id: "ENG-123") { id identifier title description priority state { id name type } assignee { id name } team { key } project { name } labels { nodes { name } } comments { nodes { body user { name } createdAt } } url } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issueSearch(query: "bug login", first: 10) { nodes { identifier title state { name } assignee { name } url } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issues(filter: { state: { type: { in: ["started"] } } }, first: 20) { nodes { identifier title state { name } assignee { name } } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issues(filter: { team: { key: { eq: "ENG" } }, assignee: { email: { eq: "user@example.com" } } }, first: 20) { nodes { identifier title state { name } priority } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ projects(first: 20) { nodes { id name description progress lead { name } teams { nodes { key } } url } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ users { nodes { id name email active } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issueLabels { nodes { id name color } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{
"query": "mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier title url } } }",
"variables": {
"input": {
"teamId": "TEAM_UUID",
"title": "Fix login bug",
"description": "Users cannot login with SSO",
"priority": 2
}
}
}' | python3 -m json.tool
先从上面的工作流状态查询中获取目标状态的 UUID,然后:
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { issueUpdate(id: "ENG-123", input: { stateId: "STATE_UUID" }) { success issue { identifier state { name type } } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { issueUpdate(id: "ENG-123", input: { assigneeId: "USER_UUID" }) { success issue { identifier assignee { name } } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { issueUpdate(id: "ENG-123", input: { priority: 1 }) { success issue { identifier priority } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { commentCreate(input: { issueId: "ISSUE_UUID", body: "Investigated. Root cause is X." }) { success comment { id body } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { issueUpdate(id: "ENG-123", input: { dueDate: "2026-04-01" }) { success issue { identifier dueDate } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { issueUpdate(id: "ENG-123", input: { labelIds: ["LABEL_UUID_1", "LABEL_UUID_2"] }) { success issue { identifier labels { nodes { name } } } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "mutation { issueUpdate(id: "ENG-123", input: { projectId: "PROJECT_UUID" }) { success issue { identifier project { name } } } }"}' | python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{
"query": "mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id name url } } }",
"variables": {
"input": {
"name": "Q2 Auth Overhaul",
"description": "Replace legacy auth with OAuth2 and PKCE",
"teamIds": ["TEAM_UUID"]
}
}
}' | python3 -m json.tool
Linear 文档是与问题存放在一起的散文式文档(RFC、规格、笔记)。它们有自己的 documents 根查询和 document(id:) 单条获取。
文档 URL 形如:
https://linear.app//document/-
末尾的十六进制段就是 slugId。示例:https://linear.app/nousresearch/document/rfc-hermes-permission-gateway-discord-38359beef67c → slugId 是 38359beef67c。
重要的 schema 细节:Markdown 正文在 content 字段中。ProseMirror JSON 在 contentState 中(不是 contentData——该字段不存在,API 会返回 400)。
document(id:) 只接受 UUID。要按 URL 中的十六进制 slug 获取,过滤集合:
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "query($s: String!) { documents(filter: { slugId: { eq: $s } }, first: 1) { nodes { id title content contentState slugId url creator { name } project { name } updatedAt } } }", "variables": {"s": "38359beef67c"}}'
| python3 -m json.tool
或者用 Python 辅助脚本:
python3 scripts/linear_api.py get-document 38359beef67c
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ document(id: "11700cff-b514-4db3-afcc-3ed1afacba1c") { title content url } }"}'
| python3 -m json.tool
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ documents(first: 25, orderBy: updatedAt) { nodes { id title slugId url updatedAt project { name } } } }"}'
| python3 -m json.tool
Linear 的 schema 没有 searchDocuments 根查询。改用标题子串过滤器:
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ documents(filter: { title: { containsIgnoreCase: "RFC" } }, first: 25) { nodes { title slugId url } } }"}'
| python3 -m json.tool
Linear 使用 Relay 风格的游标分页:
# First page
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issues(first: 20) { nodes { identifier title } pageInfo { hasNextPage endCursor } } }"}' | python3 -m json.tool
# Next page — use endCursor from previous response
curl -s -X POST https://api.linear.app/graphql
-H "Authorization: $LINEAR_API_KEY"
-H "Content-Type: application/json"
-d '{"query": "{ issues(first: 20, after: "CURSOR_FROM_PREVIOUS") { nodes { identifier title } pageInfo { hasNextPage endCursor } } }"}' | python3 -m json.tool
默认页大小:50。最大:250。始终使用 first: N 限制结果数。
比较器:eq、neq、in、nin、lt、lte、gt、gte、contains、startsWith、containsIgnoreCase
用 or: [...] 组合过滤器实现 OR 逻辑(过滤器对象内部默认是 AND)。
first: N 限制结果数,降低复杂度成本X-RateLimit-Requests-Remaining 响应头terminal 工具 + curl——不要用 web_extract 或 browsererrors 数组——HTTP 200 也可能包含错误stateId,Linear 默认为第一个 backlog 状态description 字段支持 Markdownpython3 -m json.tool 或 jq 格式化 JSON 响应以便阅读
评论区