Agent skill
af-query-linear-api
Query and interact with Linear for issue tracking and workflow management. Use when running GraphQL queries, managing team operations, or performing advanced Linear API operations beyond linearis CLI.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/af-query-linear-api
SKILL.md
Linear API Expertise
Tool Preference Order
linearisCLI (recommended) - Simple, handles state name→UUID automatically- GraphQL API - For operations the CLI doesn't support
- Linear MCP - Avoid, currently flaky
CLI vs GraphQL Capabilities
| Operation | linearis CLI | GraphQL API |
|---|---|---|
| Read/search issues | ✅ | ✅ |
| Update issue state | ✅ | ✅ |
| Create issues | ✅ | ✅ |
| Add comments | ✅ | ✅ |
| List teams | ✅ | ✅ |
| Create teams | ❌ | ✅ |
| Create workflow states | ❌ | ✅ |
| Delete/archive states | ❌ | ✅ |
| Issue relations | ❌ | ✅ |
| Issue history | ❌ | ✅ |
| Create labels | ❌ | ✅ |
| Team setup scripts | ❌ | ✅ |
Use CLI for day-to-day operations. Use GraphQL for team/project setup and advanced queries.
When to Use This Skill
Load this skill when you need to:
- Create, read, update, or search Linear issues
- Transition issues through workflow states
- Add comments to issues
- Manage workflow states (create custom statuses)
- Query team information
Quick Reference - linearis CLI
For most common operations, use the CLI:
# Read issue
linearis issues read AF-96
# Update state
linearis issues update AF-96 --state "In Progress"
linearis issues update AF-96 --state "Waiting for Feedback"
# Add comment
linearis issues comment AF-96 "Progress update: completed implementation"
# Create issue - ALWAYS use team UUID, not abbreviation (abbreviations route to wrong team)
# Look up UUID: project-registry <project> linear.team_id
linearis issues create --team "79b48ab6-d677-4f3a-8228-86e30ce923a3" "[Feature] New capability" -d "Details..."
# Search
linearis issues search "team:AF state:\"In Progress\""
GraphQL API (for advanced queries)
Use GraphQL when the CLI doesn't support the operation (e.g., history, relations, team setup).
API Endpoint
https://api.linear.app/graphql
Authentication
# Via environment variable (recommended)
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{"query": "..."}'
Get your API key: Linear Settings → Security & Access → API → Personal API keys
Issue ID Formats
- UUID:
2d40be5c-72ea-432e-a222-e169c7e8e21d(internal) - Identifier:
AF-96(human-readable, works in most queries)
Part 1: Reading Data
Get Single Issue
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query GetIssue($id: String!) { issue(id: $id) { id identifier title description state { name } assignee { name } team { key name } labels { nodes { name } } comments { nodes { body createdAt user { name } } } } }",
"variables": { "id": "AF-96" }
}' | jq
Search Issues
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query SearchIssues($query: String!, $first: Int) { searchIssues(query: $query, first: $first) { nodes { id identifier title state { name } } } }",
"variables": { "query": "team:AF state:\"In Progress\"", "first": 10 }
}' | jq
Search syntax:
team:AF- Filter by team keystate:"In Progress"- Filter by state nameassignee:@me- Assigned to current userlabel:bug- Has specific label
List Teams
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "{ teams { nodes { id key name } } }"
}' | jq
Get Workflow States for a Team
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query GetStates($teamId: String!) { team(id: $teamId) { states { nodes { id name type position color } } } }",
"variables": { "teamId": "TEAM_UUID" }
}' | jq
State types: backlog, unstarted, started, completed, canceled
Part 2: Writing Data
Update Issue State
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation UpdateIssue($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success issue { id identifier state { name } } } }",
"variables": { "id": "AF-96", "stateId": "STATE_UUID" }
}' | jq
To find state UUID, query workflow states for the team first.
Create Issue
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation CreateIssue($title: String!, $teamId: String!, $description: String) { issueCreate(input: { title: $title, teamId: $teamId, description: $description }) { success issue { id identifier url } } }",
"variables": {
"title": "[Feature] - New capability description",
"teamId": "TEAM_UUID",
"description": "## Business Context\n\n## Acceptance Criteria\n- [ ] Criterion 1"
}
}' | jq
Add Comment to Issue
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation AddComment($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success comment { id body } } }",
"variables": {
"issueId": "AF-96",
"body": "Progress update:\n- Completed initial implementation\n- Tests passing"
}
}' | jq
Create Workflow State
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation CreateState($teamId: String!, $name: String!, $type: String!, $color: String!) { workflowStateCreate(input: { teamId: $teamId, name: $name, type: $type, color: $color }) { success workflowState { id name type color } } }",
"variables": {
"teamId": "TEAM_UUID",
"name": "Waiting for Feedback",
"type": "started",
"color": "#f59e0b"
}
}' | jq
State types:
backlog- Not started, low priorityunstarted- Not started, scheduledstarted- In progresscompleted- Done successfullycanceled- Won't do
Part 3: Helper Scripts
Get State ID by Name
# Get the UUID for a state by name (e.g., "In Progress")
get_state_id() {
local team_id="$1"
local state_name="$2"
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{
\"query\": \"query { team(id: \\\"$team_id\\\") { states { nodes { id name } } } }\"
}" | jq -r ".data.team.states.nodes[] | select(.name == \"$state_name\") | .id"
}
# Usage: get_state_id "TEAM_UUID" "In Progress"
Update Issue State by Name
# Combined: update issue to a named state
update_issue_state() {
local issue_id="$1"
local state_name="$2"
# First get the issue's team
local team_id=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{\"query\": \"{ issue(id: \\\"$issue_id\\\") { team { id } } }\"}" \
| jq -r '.data.issue.team.id')
# Get the state ID
local state_id=$(get_state_id "$team_id" "$state_name")
# Update the issue
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{
\"query\": \"mutation { issueUpdate(id: \\\"$issue_id\\\", input: { stateId: \\\"$state_id\\\" }) { success } }\"
}" | jq
}
# Usage: update_issue_state "AF-96" "In Progress"
Part 4: AgentFlow Workflow Patterns
Linear States in AgentFlow
Discovered → Refining → Approved → In Progress → In Review → Dev → Test → Live
│ │ │ │ │ │ │ │
backlog started unstarted started started ←── completed ──→
State Transitions
| From | To | When |
|---|---|---|
| Discovered | Refining | Starting requirements analysis |
| Refining | Approved | Human approves mini-PRD |
| Approved | In Progress | Developer starts work |
| In Progress | In Review | PR created |
| In Review | Dev | PR merged to develop |
| Dev | Test | Deployed to test |
| Test | Live | Deployed to production |
Common Patterns
Starting work:
# Update to "In Progress"
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{"query": "mutation { issueUpdate(id: \"AF-96\", input: { stateId: \"STATE_UUID_FOR_IN_PROGRESS\" }) { success } }"}'
Waiting for feedback:
# Update to "Waiting for Feedback" (custom state)
# Add comment explaining what's needed
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{"query": "mutation { commentCreate(input: { issueId: \"AF-96\", body: \"Waiting for approval on approach\" }) { success } }"}'
PR created:
# Update to "In Review" and add PR link
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{"query": "mutation { issueUpdate(id: \"AF-96\", input: { stateId: \"STATE_UUID_FOR_IN_REVIEW\" }) { success } }"}'
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{"query": "mutation { commentCreate(input: { issueId: \"AF-96\", body: \"PR created: https://github.com/...\" }) { success } }"}'
Applying approval labels:
# Get label ID by name
LABEL_ID=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{"query": "{ issueLabels(filter: { name: { eq: \"approval:bdd-pending\" } }) { nodes { id name } } }"}' \
| jq -r '.data.issueLabels.nodes[0].id')
# Apply label to issue (adds to existing labels)
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{\"query\": \"mutation { issueAddLabel(id: \\\"ISSUE_ID\\\", labelId: \\\"$LABEL_ID\\\") { success } }\"}"
# Remove label from issue
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{\"query\": \"mutation { issueRemoveLabel(id: \\\"ISSUE_ID\\\", labelId: \\\"$LABEL_ID\\\") { success } }\"}"
Part 5: Team Setup
AgentFlow Workflow State Definitions
AgentFlow uses a standardized set of workflow states. Colors are from the reference implementation (MIN project).
| State | Type | Color | Position | Purpose |
|---|---|---|---|---|
| Discovered | backlog | #bec2c8 | 0 | New feature identified, needs exploration |
| Refining | started | #5e6ad2 | 1 | Refinement phase in progress |
| Approved | unstarted | #bec2c8 | 2 | Refinement approved, ready for implementation |
| In Progress | started | #f2c94c | 3 | Active development |
| Waiting for Feedback | started | #eb5757 | 4 | Blocked on human input (agent automation) |
| In Review | started | #0f783c | 5 | PR created, code review |
| Dev | completed | #f2c94c | 6 | Merged to develop (feature branch only) |
| Test | completed | #26b5ce | 7 | Deployed to test environment (feature branch only) |
| Live | completed | #5e6ad2 | 8 | Released to production |
| Canceled | canceled | #95a2b3 | 9 | Won't do |
| Duplicate | canceled | #95a2b3 | 10 | Duplicate of another issue |
Development Model States
Trunk-based development (single environment):
- All states except Dev and Test
- Flow: ... → In Review → Live
Feature branch development (multiple environments):
- All states including Dev and Test
- Flow: ... → In Review → Dev → Test → Live
Create Team
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation CreateTeam($name: String!, $key: String!, $cyclesEnabled: Boolean) { teamCreate(input: { name: $name, key: $key, cyclesEnabled: $cyclesEnabled }) { success team { id key name } } }",
"variables": {
"name": "My Project",
"key": "MPR",
"cyclesEnabled": true
}
}' | jq
Key rules:
keymust be 2-5 uppercase letterskeymust be unique across the organizationcyclesEnabled: truerecommended for sprint planning
Delete Default Workflow State
After creating a team, Linear adds default states (Backlog, Todo, In Progress, Done, Canceled). Delete the ones that conflict with AgentFlow states.
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DeleteState($stateId: String!) { workflowStateArchive(id: $stateId) { success } }",
"variables": { "stateId": "STATE_UUID_TO_DELETE" }
}' | jq
Note: You cannot delete a state if issues are assigned to it. Move issues first or use workflowStateArchive instead.
Create Label
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation CreateLabel($teamId: String!, $name: String!, $color: String) { issueLabelCreate(input: { teamId: $teamId, name: $name, color: $color }) { success issueLabel { id name } } }",
"variables": {
"teamId": "TEAM_UUID",
"name": "phase:discovery",
"color": "#5e6ad2"
}
}' | jq
AgentFlow Standard Labels
| Label | Color | Purpose |
|---|---|---|
| phase:setup | #95a2b3 | Setup phase work |
| phase:discovery | #5e6ad2 | Discovery phase work |
| phase:requirements | #f2c94c | Refinement phase work |
| phase:delivery | #0f783c | Delivery phase work |
| type:feature | #5e6ad2 | New functionality |
| type:bug | #eb5757 | Bug fix |
| type:improvement | #26b5ce | Enhancement to existing |
| type:documentation | #bec2c8 | Documentation work |
| approval:bdd-pending | #f59e0b | BDD scenarios pending review |
| approval:bdd-approved | #0f783c | BDD scenarios approved |
| approval:ux-pending | #f59e0b | UX design pending review |
| approval:ux-approved | #0f783c | UX design approved |
Complete Team Setup Script
#!/bin/bash
# Setup AgentFlow team in Linear
# Usage: ./setup-linear-team.sh "Project Name" "KEY" [trunk|feature]
PROJECT_NAME="$1"
TEAM_KEY="$2"
DEV_MODEL="${3:-feature}" # Default to feature branch
# 1. Create team
echo "Creating team: $PROJECT_NAME ($TEAM_KEY)..."
TEAM_RESULT=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{
\"query\": \"mutation { teamCreate(input: { name: \\\"$PROJECT_NAME\\\", key: \\\"$TEAM_KEY\\\", cyclesEnabled: true }) { success team { id } } }\"
}")
TEAM_ID=$(echo "$TEAM_RESULT" | jq -r '.data.teamCreate.team.id')
if [ "$TEAM_ID" == "null" ]; then
echo "Error creating team: $(echo "$TEAM_RESULT" | jq -r '.errors[0].message')"
exit 1
fi
echo "Created team with ID: $TEAM_ID"
# 2. Get and archive default states
echo "Archiving default states..."
DEFAULT_STATES=$(curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{\"query\": \"{ team(id: \\\"$TEAM_ID\\\") { states { nodes { id name } } } }\"}" \
| jq -r '.data.team.states.nodes[] | "\(.id)|\(.name)"')
while IFS='|' read -r state_id state_name; do
echo " Archiving: $state_name"
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{\"query\": \"mutation { workflowStateArchive(id: \\\"$state_id\\\") { success } }\"}" > /dev/null
done <<< "$DEFAULT_STATES"
# 3. Create AgentFlow states
echo "Creating AgentFlow workflow states..."
create_state() {
local name="$1" type="$2" color="$3" position="$4"
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{
\"query\": \"mutation { workflowStateCreate(input: { teamId: \\\"$TEAM_ID\\\", name: \\\"$name\\\", type: \\\"$type\\\", color: \\\"$color\\\", position: $position }) { success } }\"
}" > /dev/null
echo " Created: $name"
}
create_state "Discovered" "backlog" "#bec2c8" 0
create_state "Refining" "started" "#5e6ad2" 1
create_state "Approved" "unstarted" "#bec2c8" 2
create_state "In Progress" "started" "#f2c94c" 3
create_state "Waiting for Feedback" "started" "#eb5757" 4
create_state "In Review" "started" "#0f783c" 5
if [ "$DEV_MODEL" == "feature" ]; then
create_state "Dev" "completed" "#f2c94c" 6
create_state "Test" "completed" "#26b5ce" 7
fi
create_state "Live" "completed" "#5e6ad2" 8
create_state "Canceled" "canceled" "#95a2b3" 9
create_state "Duplicate" "canceled" "#95a2b3" 10
# 4. Create labels
echo "Creating labels..."
create_label() {
local name="$1" color="$2"
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "{
\"query\": \"mutation { issueLabelCreate(input: { teamId: \\\"$TEAM_ID\\\", name: \\\"$name\\\", color: \\\"$color\\\" }) { success } }\"
}" > /dev/null
echo " Created: $name"
}
create_label "phase:setup" "#95a2b3"
create_label "phase:discovery" "#5e6ad2"
create_label "phase:requirements" "#f2c94c"
create_label "phase:delivery" "#0f783c"
create_label "type:feature" "#5e6ad2"
create_label "type:bug" "#eb5757"
create_label "type:improvement" "#26b5ce"
create_label "type:documentation" "#bec2c8"
create_label "approval:bdd-pending" "#f59e0b"
create_label "approval:bdd-approved" "#0f783c"
create_label "approval:ux-pending" "#f59e0b"
create_label "approval:ux-approved" "#0f783c"
echo ""
echo "✅ Team setup complete!"
echo " Team: $PROJECT_NAME ($TEAM_KEY)"
echo " ID: $TEAM_ID"
echo " Model: $DEV_MODEL"
echo ""
echo "Add to project registry:"
echo " linear:"
echo " team_key: \"$TEAM_KEY\""
echo " team_id: \"$TEAM_ID\""
Best Practices
For AI Agents
- Cache team and state UUIDs - Query once at session start, reuse throughout
- Use issue identifiers (e.g.,
AF-96) not UUIDs for readability - Add comments for audit trail - Document decisions and progress
- Check response success - Always verify
success: truein mutations - Use search for discovery -
searchIssuesis powerful for finding related work
Error Handling
# Check for errors in response
response=$(curl -s -X POST https://api.linear.app/graphql ...)
if echo "$response" | jq -e '.errors' > /dev/null; then
echo "Error: $(echo "$response" | jq -r '.errors[0].message')"
exit 1
fi
Rate Limits
- Linear has rate limits (currently generous for most use cases)
- Batch operations when possible (
issueBatchUpdate) - Cache responses when data doesn't change frequently
Troubleshooting
"Entity not found"
- Check issue identifier is correct (case-sensitive)
- Ensure you have access to the team
"Invalid state transition"
- Some Linear workflows enforce state ordering
- Check team's workflow settings
"Unauthorized"
- Verify
LINEAR_API_KEYis set and valid - Check API key has required scopes
Essential Reading
- Linear GraphQL API Docs
- Work Management Guide
- Work Management Expertise
Remember:
- Prefer
linearisCLI for common operations (read, update state, comment, create) - Use GraphQL API only for advanced operations (history, relations, team setup)
- Avoid Linear MCP - currently flaky
- Issue identifiers (e.g.,
AF-96) work in CLI and most GraphQL queries - Always add comments for traceability
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?