Linear issue tracker API integration. Covers first-use identity bootstrap (viewer + teams cached), raw GraphQL for list/search/create/update, and the rules for handling "my issues" / "assigned to me" requests.
71
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
You have access to the Linear GraphQL API via the http tool. Credentials are automatically injected — never construct Authorization headers manually. When the URL host is api.linear.app, the system injects Authorization: {linear_api_key} transparently (no Bearer prefix — Linear API keys are sent raw).
Linear's API key does not tell you who the user IS inside Linear. Before running any "my issues" / "assigned to me" / "my tickets" request, make sure the user's Linear identity is cached. This avoids re-fetching viewer on every request and makes filter-by-assignee queries deterministic.
Path: context/intel/linear-identity.md
Shape:
---
type: linear-identity
bootstrapped_at: 2026-04-21
refreshed_at: 2026-04-21
stale_after: 2026-05-21
---
# Linear identity
user_id: 8a7f...-uuid
display_name: Tobias Holenstein
email: tobias@...
timezone: Europe/Zurich
## Teams
- id: team-uuid-a, key: ENG, name: Engineering
- id: team-uuid-b, key: PROD, name: Product
## Default team
ENGmemory_read("context/intel/linear-identity.md"). If the file exists and stale_after is in the future, use it and stop.query { viewer { id name displayName email } teams(first: 50) { nodes { id key name } } }memory_write with stale_after = today + 30 days.Default team. If more than one, ask the user once: "I see teams ENG, PROD, OPS. Which one do you default to for new issues?" and store the answer.AuthenticationError GraphQL error, invalidate the cache and re-prompt the user to check their API key — do not silently retry.assignee: { id: { eq: "<cached user_id>" } }, not by assignee: { isMe: true } (the isMe filter is not universally available and viewer round-trips are wasteful).Default team id without asking.Linear uses a single GraphQL endpoint: https://api.linear.app/graphql
All requests are POST with a JSON body containing query and optional variables.
http(method="POST", url="https://api.linear.app/graphql", body={"query": "{ issues(first: 20, orderBy: updatedAt) { nodes { id identifier title state { name } assignee { name } priority priorityLabel createdAt } } }"})http(method="POST", url="https://api.linear.app/graphql", body={"query": "query($uid: ID!) { issues(filter: { assignee: { id: { eq: $uid } }, state: { type: { nin: [completed, canceled] } } }, first: 50, orderBy: updatedAt) { nodes { id identifier title state { name type } priority priorityLabel url updatedAt } } }", "variables": {"uid": "<cached user_id>"}})Never pass viewer.id inline from a fresh round-trip when the cache is valid — consult context/intel/linear-identity.md.
http(method="POST", url="https://api.linear.app/graphql", body={"query": "query($id: String!) { issue(id: $id) { id identifier title description state { name } assignee { name } labels { nodes { name } } comments { nodes { body user { name } createdAt } } } }", "variables": {"id": "ISSUE_ID"}})http(method="POST", url="https://api.linear.app/graphql", body={"query": "query($term: String!) { issueSearch(query: $term, first: 10) { nodes { id identifier title state { name } priorityLabel } } }", "variables": {"term": "SEARCH_TERM"}})http(method="POST", url="https://api.linear.app/graphql", body={"query": "mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier title url } } }", "variables": {"input": {"title": "...", "description": "...", "teamId": "TEAM_ID", "priority": 2}}})http(method="POST", url="https://api.linear.app/graphql", body={"query": "{ teams { nodes { id name key } } }"})http(method="POST", url="https://api.linear.app/graphql", body={"query": "mutation($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success issue { id identifier title state { name } } } }", "variables": {"id": "ISSUE_UUID", "stateId": "STATE_UUID"}}){"data": {...}} on success, {"errors": [...]} on failure.ENG-123 (team key + number).errors in the response before processing data.message and optional extensions with error codes.Authorization header — it is injected automatically.POST method — Linear's API is GraphQL only.id field is a UUID, the identifier field is human-readable (e.g., ENG-42).issueSearch for text search, not issues with a filter (text search is separate).teamId. List teams first if unknown.b60038b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.