Linear Integration
9 Linear agent tools for querying issues, cycles, projects, and updating tickets — plus OAuth, team selection, and recommendation push.
Overview
Linear is a first-class execution surface for Praxiom AI. Beyond OAuth + recommendation push, the agent has nine tools for live querying and writing — it can answer "what's Alex working on this sprint?" by querying the Linear GraphQL API directly, surface duplicates before creating a new issue, and push state/assignee/cycle updates through the HITL approval layer.
Connecting
Initiate OAuth
POST /api/integrations/linear/connect{ "workspace_id": "your-workspace-uuid" }Returns { auth_url, state }. OAuth state rows expire after 10 minutes.
Handle callback
Linear redirects to GET /api/integrations/linear/callback?code=...&state=.... The backend exchanges the code, fetches the viewer (user + org), encrypts the token, and upserts the Integration with integration_type="linear". Config stores linear_user_id, linear_user_name, linear_user_email, linear_org_id, linear_org_name, linear_org_logo_url, plus placeholders for selected_team_id, selected_team_name, selected_project_id, selected_project_name.
Select team and optional project
List accessible teams with their nested projects, workflow states (sorted by position), and labels:
GET /api/integrations/linear/teams?workspace_id={id}Save the pick:
POST /api/integrations/linear/teams/select{
"workspace_id": "your-workspace-uuid",
"team_id": "team-uuid",
"team_name": "Engineering",
"project_id": "optional-project-uuid",
"project_name": "Onboarding rewrite"
}The selected team scopes create_linear_issue, query_linear_issues (unless team_scope=false), and workflow-state resolution for update_linear_issue.
Linear is gated on the has_integrations feature flag. Tokens are encrypted at rest.
Agent Tools
All nine tools live in app/core/sdk/tools/integration.py. Four tools are @gated — they queue in pending actions rather than executing immediately.
| Tool | Args | Returns | HITL | Purpose |
|---|---|---|---|---|
create_linear_issue | workspace_id, title, description, recommendation_id?, priority? | issue_id, issue_identifier, issue_url, title, linked_recommendation | Yes | Create an issue, optionally linked to a recommendation (auto-populates from build_linear_issue_body). priority integer: 0=none, 1=urgent, 2=high, 3=medium, 4=low. |
query_linear_issues | workspace_id, state_type?, assignee_name?, project_name?, cycle?, label?, limit?, include_completed? | issues[], count, filters_applied | No | Rich query. state_type enum: backlog, unstarted, started, completed, cancelled, triage. cycle accepts active (resolved to the team's active cycle) or a UUID. Assignee/project/label are resolved by substring match. limit default 30, max 100. |
search_linear_issues | workspace_id, query, limit?, team_scope? | results[], count, query | No | Full-text search. team_scope=true (default) scopes to the selected team; false hits the org. limit default 10, max 50. Use before creating to surface duplicates. |
get_linear_cycle | workspace_id, cycle_id? | cycle, issues[], progress (dict with completion stats) | No | A sprint with its issues + progress dict. Omit cycle_id to default to the active cycle for the selected team. |
get_linear_project | workspace_id, project_id? | List mode: projects[], count. Detail mode: project, milestones[], issues[] | No | Omit project_id to list active projects for the team; pass one to get full detail. |
get_linear_issue | workspace_id, identifier | issue, synced boolean, optional assignee, priority, labels | No | Fetch one linked issue by identifier (e.g. ENG-42). Opportunistically syncs fresh state from Linear when a token is available. |
list_linear_issues | workspace_id, status?, limit? | issues[], count, filter | No | List cached recommendation-linked issues. status enum: all, open (not completed/cancelled), closed. limit clamped 1–200. |
update_linear_issue | workspace_id, identifier, state?, assignee_name?, priority?, title?, description?, due_date?, cycle? | issue, message | Yes | Update up to 8 fields. Names are resolved server-side: state against team workflow states, assignee_name via find_user_by_name, cycle accepts active, none, or a UUID. due_date format: YYYY-MM-DD. |
create_linear_comment | workspace_id, identifier, body | comment_id, issue_identifier, url | Yes | Post a Markdown comment. Body capped at 4000 chars in the approval preview. |
Error shape
Tools translate LinearServiceError into structured responses:
{
"success": false,
"error": "No user matched 'alx'. Ask the user to clarify.",
"error_code": "user_not_found"
}
Common error_code values: integration_not_connected, no_team_selected, not_found, validation_error, user_not_found, project_not_found, label_not_found, no_active_cycle, rate_limited, token_expired.
Cycle resolution
query_linear_issues and update_linear_issue both accept cycle as a string. The handler resolves:
"active"→ the current cycle for the selected team (viaservice.get_active_cycle(team_id)). If there's no active cycle,query_linear_issuesreturns an empty list with a friendly message."none"(onlyupdate_linear_issue) → unset the cycle.- any other value → treated as a cycle UUID.
Creating Issues (API)
POST /api/integrations/linear/issues
{
"workspace_id": "your-workspace-uuid",
"recommendation_id": "rec-uuid",
"title": "Improve onboarding flow",
"body": "Based on user research...",
"project_id": "optional-project-id",
"label_ids": ["label-id-1"],
"priority": 2
}
Requires a selected team (else NO_TEAM_SELECTED). Falls back to selected_project_id from config if project_id is omitted. Returns issue_url, issue_identifier (e.g. ENG-42), and link_id. Persists a RecommendationLinearLink with the issue's current state + state type for sync.
Status Sync
POST /api/integrations/linear/sync
For every link in the workspace, calls LinearService.get_issue and refreshes linear_issue_state + linear_issue_state_type. When state_type transitions to completed or cancelled, the linked Recommendation.status is updated to implemented (unless already implemented or rejected).
Response: { synced_count, updated_count }.
Management Endpoints
| Endpoint | Purpose |
|---|---|
GET /api/integrations/linear/status?workspace_id={id} | connected, linear_user_name, linear_org_name, linear_org_logo_url, selected_team_id, selected_team_name, selected_project_id, selected_project_name, last_synced_at |
GET /api/integrations/linear/teams?workspace_id={id} | Teams + nested projects, states (sorted), labels |
POST /api/integrations/linear/teams/select | Save selected team + project |
POST /api/integrations/linear/issues | Create issue + link |
GET /api/integrations/linear/links?workspace_id={id}&recommendation_id=... | List recommendation–issue links |
POST /api/integrations/linear/sync | Bulk sync |
DELETE /api/integrations/linear/disconnect | Removes all links + the Integration row |
Key Concepts
- Name-based resolution — agents work in human names (
"Alex","In Progress","Onboarding") and the tool resolves them to Linear IDs server-side. If resolution fails, the agent gets a structured error with the available set. - HITL on every write —
create_linear_issue,update_linear_issue,create_linear_comment, plus recommendation-sourced issue creation go through pending actions. The preview captures the intended change set (field + new value) before execution. - Read tools are free —
query_linear_issues,search_linear_issues,get_linear_cycle,get_linear_project,get_linear_issue,list_linear_issuesall run immediately with no user approval. Use them liberally to ground recommendations in the current team state. - Priority mapping — the API matches Linear's native integer scheme (
0=none, 1=urgent, 2=high, 3=medium, 4=low). The execution-ticket pipeline maps Praxiom'sP0/P1/P2/P3to1/2/3/4.
What's Next
- GitHub Integration — 7 read tools for grounding in code.
- Pending Actions — how
@gatedwrite tools queue for approval. - Execution Tickets — bulk push a whole spec, priority-mapped, to Linear.
Was this helpful?