Skip to main content

Linear Tracker Plugin

The Linear tracker plugin integrates with Linear’s issue tracking system, allowing Agent Orchestrator to manage tasks, track progress, and update issue state as agents work.

Overview

This plugin uses Linear’s GraphQL API and supports two authentication methods:
  1. Direct API: Using LINEAR_API_KEY environment variable
  2. Composio SDK: Using COMPOSIO_API_KEY for integrated workflow automation
The plugin automatically detects which method is available and routes requests accordingly.
Linear uses alphanumeric identifiers (e.g., INT-1234) rather than numeric issue numbers. Branch names follow the convention feat/INT-1234.

Configuration

Configure the Linear tracker plugin in your agent-orchestrator.yaml:
plugins:
  tracker:
    name: linear

projects:
  - id: my-app
    repo: owner/repo-name
    tracker:
      type: linear
      teamId: "abc123-team-uuid"        # Required: Linear team UUID
      workspaceSlug: "my-workspace"     # Optional: for prettier URLs

Configuration Options

teamId
string
required
Linear team UUID. Required for creating issues and filtering by team.Find your team ID in Linear’s URL or via the API.
workspaceSlug
string
Linear workspace URL slug for generating issue URLs.Example: If your Linear URL is https://linear.app/my-workspace/issue/INT-123, the slug is my-workspace.

Requirements

Prerequisites:
  • LINEAR_API_KEY environment variable (for direct API access) OR
  • COMPOSIO_API_KEY environment variable (for Composio SDK)
  • Network access to https://api.linear.app/graphql

Authentication Setup

Option 1: Direct API Key
# Get your API key from Linear settings:
# https://linear.app/settings/api

export LINEAR_API_KEY="lin_api_xxxxxxxxxxxxxx"
Option 2: Composio SDK
# Install Composio SDK
pnpm add @composio/core

# Set API keys
export COMPOSIO_API_KEY="your-composio-key"
export COMPOSIO_ENTITY_ID="default"  # Optional, defaults to "default"

Finding Your Team ID

# Run this query in Linear's API explorer:
# https://linear.app/settings/api

query {
  teams {
    nodes {
      id
      name
      key
    }
  }
}
Or use the Linear CLI:
# Linear URL contains team key (e.g., INT)
# https://linear.app/my-workspace/team/INT/active

Features

Issue Operations

Get Issue Fetch a single issue by identifier:
const issue = await tracker.getIssue("INT-1234", project);
// Returns: { id, title, description, url, state, labels, assignee, priority }
List Issues List issues with filters:
const issues = await tracker.listIssues({
  state: "open",                    // "open" | "closed" | "all"
  labels: ["bug", "frontend"],     // Filter by labels
  assignee: "John Doe",             // Filter by display name
  limit: 30                         // Max results (default: 30)
}, project);
The Linear API automatically filters by teamId from the project config, so you only see issues for your team.
Create Issue Create a new issue:
const issue = await tracker.createIssue({
  title: "Bug: App crashes on startup",
  description: "## Steps to Reproduce\n\n1. Open app\n2. Crashes",
  labels: ["bug", "high-priority"],
  assignee: "John Doe",
  priority: 1  // 0=None, 1=Urgent, 2=High, 3=Normal, 4=Low
}, project);
Update Issue Update issue state, labels, assignee, or add comments:
await tracker.updateIssue("INT-1234", {
  state: "closed",                  // "open" | "in_progress" | "closed"
  labels: ["fixed"],                // Adds labels (additive)
  assignee: "Jane Smith",           // Change assignee by display name
  comment: "Fixed in PR #456"       // Add comment
}, project);

Issue State Mapping

Linear State Type → Tracker State:
Linear StateTracker State
triageopen
backlogopen
unstartedopen
startedin_progress
completedclosed
canceledcancelled
Linear has rich workflow states. The plugin maps them to standardized tracker states while preserving Linear’s native state information.

Priority Levels

Linear uses numeric priorities:
PriorityValueTypical Use
No priority0Unset
Urgent1Critical bugs
High2Important features
Normal3Standard work
Low4Nice-to-have

Prompt Generation

The plugin generates context-aware prompts for agents:
const prompt = await tracker.generatePrompt("INT-1234", project);
Example output:
You are working on Linear ticket INT-1234: Fix login redirect bug
Issue URL: https://linear.app/my-workspace/issue/INT-1234

Labels: bug, authentication
Priority: Urgent

## Description

Users are redirected to the wrong page after login...

Please implement the changes described in this ticket. When done, commit and push your changes.

Branch Naming

The plugin generates branch names from issue identifiers:
tracker.branchName("INT-1234", project);
// Returns: "feat/INT-1234"
This follows Linear’s recommended convention for branch names.

Issue URLs and Labels

// Generate URL from issue identifier
tracker.issueUrl("INT-1234", project);
// Returns: "https://linear.app/my-workspace/issue/INT-1234"

// Extract identifier from URL
tracker.issueLabel("https://linear.app/my-workspace/issue/INT-1234", project);
// Returns: "INT-1234"

Usage Example

Start Agent on Issue

# agent-orchestrator.yaml
projects:
  - id: my-app
    repo: owner/my-app
    path: ~/repos/my-app
    defaultBranch: main
    tracker:
      type: linear
      teamId: "abc123-team-uuid"
      workspaceSlug: "my-workspace"
# Start agent on Linear issue
ao start my-app "INT-1234"

# The agent will:
# 1. Fetch issue details from Linear
# 2. Create branch "feat/INT-1234"
# 3. Generate prompt with issue context (including priority)
# 4. Start coding to fix the issue

Auto-Update on Progress

# Configure reactions to update Linear when work starts
reactions:
  - event: session_started
    action: run
    command: |
      # Update Linear issue to "In Progress"
      ao tracker update $ISSUE_ID --state in_progress

Troubleshooting

The plugin couldn’t find authentication credentials.Solution:
# Option 1: Direct API
export LINEAR_API_KEY="lin_api_xxxxxxxxxxxxxx"

# Option 2: Composio SDK
pnpm add @composio/core
export COMPOSIO_API_KEY="your-composio-key"
The teamId field is missing from project configuration.Solution:
projects:
  - id: my-app
    tracker:
      type: linear
      teamId: "abc123-team-uuid"  # Add this
Find your team ID in Linear’s API explorer.
You’re using COMPOSIO_API_KEY but the SDK isn’t installed.Solution:
pnpm add @composio/core
Or switch to direct API with LINEAR_API_KEY.
Your Linear team doesn’t have a workflow state of the required type.Linear requires specific workflow state types:
  • unstarted for “open”
  • started for “in_progress”
  • completed for “closed”
Solution: Check your team’s workflow settings in Linear and ensure you have states of each type.
Linear API requests have a 30-second timeout.Solutions:
  • Check network connectivity
  • Verify Linear API status: https://status.linear.app/
  • Check API rate limits (direct API has limits)
  • Use Composio SDK for better rate limit handling
The plugin looks up users by displayName (not email or ID).Solution:
// Use the exact display name from Linear
await tracker.updateIssue("INT-1234", {
  assignee: "John Smith"  // Must match Linear display name exactly
}, project);
Find display names in Linear’s user settings.
The plugin looks up labels by name for your team.Solution:
  • Create labels in Linear first (Settings → Labels)
  • Use exact label names (case-sensitive)
  • Labels are best-effort: if not found, issue is still created without them

API Reference

Tracker Interface Methods

getIssue(identifier, project)
  • Fetches issue details by Linear identifier
  • Returns: Issue object with all fields including priority
isCompleted(identifier, project)
  • Checks if issue is in completed or canceled state
  • Returns: boolean
issueUrl(identifier, project)
  • Generates Linear issue URL
  • Returns: string
issueLabel(url, project)
  • Extracts issue identifier from URL
  • Returns: string (e.g., "INT-1234")
branchName(identifier, project)
  • Generates branch name from identifier
  • Returns: string (e.g., "feat/INT-1234")
generatePrompt(identifier, project)
  • Creates context prompt for agent
  • Returns: string (formatted prompt with priority)
listIssues(filters, project)
  • Lists issues with optional filters
  • Automatically filters by teamId
  • Returns: Issue[]
updateIssue(identifier, update, project)
  • Updates issue state/labels/assignee/comments
  • Handles workflow state transitions
  • Returns: void
createIssue(input, project)
  • Creates a new issue
  • Returns: Issue (created issue)

Advanced Usage

GraphQL Transport

The plugin uses a transport abstraction that supports two backends:
// Direct Linear API
const transport = createDirectTransport();

// Composio SDK
const transport = createComposioTransport(apiKey, entityId);

// Both implement the same interface
const data = await transport<{ issue: LinearIssue }>(query, variables);

Custom Workflow States

When updating issue state, the plugin:
  1. Queries your team’s workflow states
  2. Finds the appropriate state by type
  3. Updates the issue to that state
// This finds your team's "started" state and uses it
await tracker.updateIssue("INT-1234", {
  state: "in_progress"
}, project);

Label Management

Labels are additive: using updateIssue with labels merges them with existing labels, it doesn’t replace them.
// Add labels to an issue
await tracker.updateIssue("INT-1234", {
  labels: ["in-progress", "needs-review"]
}, project);

// Existing labels are preserved

Priority Workflow

// Create high-priority issue
const issue = await tracker.createIssue({
  title: "Critical bug",
  description: "Production is down",
  priority: 1,  // Urgent
  labels: ["bug", "production"]
}, project);

// Priority is included in generated prompts
const prompt = await tracker.generatePrompt(issue.id, project);
// Contains: "Priority: Urgent"

Comment Workflow

// Post status update
await tracker.updateIssue("INT-1234", {
  comment: "🚀 Started working on this issue"
}, project);

// Post completion comment
await tracker.updateIssue("INT-1234", {
  state: "closed",
  comment: "✅ Fixed in PR #456\n\nAll tests passing."
}, project);

Integration with Other Plugins

With SCM Plugin

The tracker and SCM plugins work together for full issue-to-PR workflow:
  1. Tracker generates prompt from Linear issue (with priority context)
  2. Agent writes code
  3. SCM creates PR with Linear issue link
  4. Reactions auto-close issue when PR merges
reactions:
  - event: pr_merged
    action: run
    command: |
      # Linear's smart PR integration automatically closes issues
      # when PR title contains issue identifier (e.g., "INT-1234: Fix bug")

With Workspace Plugin

The tracker provides the branch name for workspace creation:
const branchName = tracker.branchName("INT-1234", project);
// Used by workspace plugin to create branch "feat/INT-1234"

With Composio

When using Composio SDK, you get additional benefits:
  • Centralized credential management
  • Workflow automation triggers
  • Multi-tool integrations
  • Better rate limit handling

Performance Considerations

GraphQL Queries

The plugin uses optimized GraphQL queries:
  • Fetches only required fields
  • Uses variables to prevent injection
  • Batches related operations when possible

Caching

The Composio SDK client is cached:
// Client is created once and reused
const client = new Composio({ apiKey });

Timeouts

All requests have a 30-second timeout to prevent hanging:
  • Direct API: native timeout in node:https
  • Composio SDK: Promise.race with timeout

Security Considerations

  • API keys are read from environment variables (never hardcoded)
  • GraphQL queries use variables (no string interpolation)
  • User input is validated before API calls
  • Team ID is required (prevents cross-team data access)
  • All requests are HTTPS-only

Source Code

Source: packages/plugins/tracker-linear/src/index.ts

Build docs developers (and LLMs) love