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

1

Initiate OAuth

POST /api/integrations/linear/connect
{ "workspace_id": "your-workspace-uuid" }

Returns { auth_url, state }. OAuth state rows expire after 10 minutes.

2

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.

3

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.

ToolArgsReturnsHITLPurpose
create_linear_issueworkspace_id, title, description, recommendation_id?, priority?issue_id, issue_identifier, issue_url, title, linked_recommendationYesCreate 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_issuesworkspace_id, state_type?, assignee_name?, project_name?, cycle?, label?, limit?, include_completed?issues[], count, filters_appliedNoRich 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_issuesworkspace_id, query, limit?, team_scope?results[], count, queryNoFull-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_cycleworkspace_id, cycle_id?cycle, issues[], progress (dict with completion stats)NoA sprint with its issues + progress dict. Omit cycle_id to default to the active cycle for the selected team.
get_linear_projectworkspace_id, project_id?List mode: projects[], count. Detail mode: project, milestones[], issues[]NoOmit project_id to list active projects for the team; pass one to get full detail.
get_linear_issueworkspace_id, identifierissue, synced boolean, optional assignee, priority, labelsNoFetch one linked issue by identifier (e.g. ENG-42). Opportunistically syncs fresh state from Linear when a token is available.
list_linear_issuesworkspace_id, status?, limit?issues[], count, filterNoList cached recommendation-linked issues. status enum: all, open (not completed/cancelled), closed. limit clamped 1–200.
update_linear_issueworkspace_id, identifier, state?, assignee_name?, priority?, title?, description?, due_date?, cycle?issue, messageYesUpdate 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_commentworkspace_id, identifier, bodycomment_id, issue_identifier, urlYesPost 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 (via service.get_active_cycle(team_id)). If there's no active cycle, query_linear_issues returns an empty list with a friendly message.
  • "none" (only update_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

EndpointPurpose
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/selectSave selected team + project
POST /api/integrations/linear/issuesCreate issue + link
GET /api/integrations/linear/links?workspace_id={id}&recommendation_id=...List recommendation–issue links
POST /api/integrations/linear/syncBulk sync
DELETE /api/integrations/linear/disconnectRemoves 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 writecreate_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 freequery_linear_issues, search_linear_issues, get_linear_cycle, get_linear_project, get_linear_issue, list_linear_issues all 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's P0/P1/P2/P3 to 1/2/3/4.

What's Next

Was this helpful?