home / skills / madappgang / claude-code / linear-integration

This skill streamlines Linear API workflows by authenticating, handling webhooks, performing issue CRUD, state transitions, attachments, and comments.

npx playbooks add skill madappgang/claude-code --skill linear-integration

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
6.2 KB
---
name: linear-integration
description: Linear API patterns and examples for autopilot. Includes authentication, webhooks, issue CRUD, state transitions, file attachments, and comment handling.
version: 0.1.0
tags: [linear, api, webhook, integration]
keywords: [linear, api, webhook, issue, comment, state, attachment]
---
plugin: autopilot
updated: 2026-01-20

# Linear Integration

**Version:** 0.1.0
**Purpose:** Patterns for Linear API integration in autopilot workflows
**Status:** Phase 1

## When to Use

Use this skill when you need to:
- Authenticate with Linear API
- Set up webhook handlers for Linear events
- Create, read, update, or delete Linear issues
- Transition issue states in Linear workflows
- Attach files to Linear issues
- Add comments to Linear issues

## Overview

This skill provides patterns for:
- Linear API authentication
- Webhook handler setup
- Issue CRUD operations
- State transitions
- File attachments
- Comment handling

## Core Patterns

### Pattern 1: Authentication

**Personal API Key (MVP):**
```typescript
import { LinearClient } from '@linear/sdk';

const linear = new LinearClient({
  apiKey: process.env.LINEAR_API_KEY
});
```

**Verification:**
```typescript
async function verifyConnection(): Promise<boolean> {
  try {
    const me = await linear.viewer;
    console.log(`Connected as: ${me.name}`);
    return true;
  } catch (error) {
    console.error('Linear connection failed:', error);
    return false;
  }
}
```

### Pattern 2: Webhook Handler

**Bun HTTP Server:**
```typescript
import { serve } from 'bun';
import { createHmac } from 'crypto';

interface LinearWebhookPayload {
  action: 'created' | 'updated' | 'deleted';
  type: 'Issue' | 'Comment' | 'Label';
  data: {
    id: string;
    title?: string;
    description?: string;
    state: { id: string; name: string };
    labels: Array<{ id: string; name: string }>;
  };
}

serve({
  port: process.env.AUTOPILOT_WEBHOOK_PORT || 3001,

  async fetch(req: Request): Promise<Response> {
    if (req.method !== 'POST') {
      return new Response('Method not allowed', { status: 405 });
    }

    // Verify signature
    const signature = req.headers.get('Linear-Signature');
    const body = await req.text();

    if (!verifySignature(body, signature)) {
      return new Response('Unauthorized', { status: 401 });
    }

    const payload: LinearWebhookPayload = JSON.parse(body);

    // Route to handler
    await routeWebhook(payload);

    return new Response('OK', { status: 200 });
  }
});

function verifySignature(body: string, signature: string | null): boolean {
  if (!signature) return false;

  const hmac = createHmac('sha256', process.env.LINEAR_WEBHOOK_SECRET!);
  const expectedSignature = hmac.update(body).digest('hex');

  return signature === expectedSignature;
}
```

### Pattern 3: Issue Operations

**Create Issue:**
```typescript
async function createIssue(
  teamId: string,
  title: string,
  description: string,
  labels: string[]
): Promise<string> {
  // Note: Linear SDK uses linear.createIssue() method
  const result = await linear.createIssue({
    teamId,
    title,
    description,
    labelIds: await resolveLabelIds(labels),
    assigneeId: process.env.AUTOPILOT_BOT_USER_ID,
    priority: 2,
  });

  const issue = await result.issue;
  return issue!.id;
}
```

**Query Issues:**
```typescript
async function getAutopilotTasks(teamId: string) {
  const issues = await linear.issues({
    filter: {
      team: { id: { eq: teamId } },
      assignee: { id: { eq: process.env.AUTOPILOT_BOT_USER_ID } },
      state: { name: { in: ['Todo', 'In Progress'] } },
    },
  });

  return issues.nodes;
}
```

### Pattern 4: State Transitions

**Transition State:**
```typescript
async function transitionState(
  issueId: string,
  newStateName: string
): Promise<void> {
  // Get workflow states for the issue's team
  const issue = await linear.issue(issueId);
  const team = await issue.team;
  const states = await team.states();

  const targetState = states.nodes.find(s => s.name === newStateName);

  if (!targetState) {
    throw new Error(`State "${newStateName}" not found`);
  }

  // Note: Linear SDK uses linear.updateIssue() method
  await linear.updateIssue(issueId, {
    stateId: targetState.id,
  });
}
```

### Pattern 5: File Attachments

**Upload and Attach:**
```typescript
async function attachFile(
  issueId: string,
  filePath: string,
  fileName: string
): Promise<void> {
  // Request upload URL
  const uploadPayload = await linear.fileUpload(
    getMimeType(filePath),
    fileName,
    getFileSize(filePath)
  );

  // Upload to storage
  const fileContent = await Bun.file(filePath).arrayBuffer();
  await fetch(uploadPayload.uploadUrl, {
    method: 'PUT',
    body: fileContent,
    headers: { 'Content-Type': getMimeType(filePath) },
  });

  // Attach to issue
  await linear.attachmentCreate({
    issueId,
    url: uploadPayload.assetUrl,
    title: fileName,
  });
}
```

### Pattern 6: Comments

**Add Comment:**
```typescript
async function addComment(
  issueId: string,
  body: string
): Promise<void> {
  // Note: Linear SDK uses linear.createComment() method
  await linear.createComment({
    issueId,
    body,
  });
}
```

## Best Practices

- Always verify webhook signatures
- Use exponential backoff for API rate limits
- Cache team/state/label IDs to reduce API calls
- Handle webhook delivery failures gracefully
- Log all state transitions for audit

## Examples

### Example 1: Full Issue Lifecycle

```typescript
// Create issue
const issueId = await createIssue(
  teamId,
  "Add user profile page",
  "Implement user profile with avatar upload",
  ["frontend", "feature"]
);

// Transition to In Progress
await transitionState(issueId, "In Progress");

// ... work happens ...

// Attach proof artifacts
await attachFile(issueId, "screenshot.png", "Desktop Screenshot");

// Add completion comment
await addComment(issueId, "Implementation complete. See attached proof.");

// Transition to In Review
await transitionState(issueId, "In Review");
```

### Example 2: Query Autopilot Queue

```typescript
const tasks = await getAutopilotTasks(teamId);

console.log(`Autopilot queue: ${tasks.length} tasks`);
for (const task of tasks) {
  console.log(`- ${task.identifier}: ${task.title} (${task.state.name})`);
}
```

Overview

This skill provides practical TypeScript patterns for integrating autopilot workflows with the Linear API. It covers authentication, webhook handling, issue CRUD, state transitions, file attachments, and comment management. The patterns are focused on reliable, production-ready flows and small code examples for quick adoption. Use it to standardize Linear interactions across automation services.

How this skill works

The skill shows how to instantiate a Linear client using an API key, verify connectivity, and handle signed webhook payloads. It provides functions to create, query, update, and delete issues, transition issue workflow states, upload and attach files, and post comments. Example code demonstrates signature verification, routing webhook events, resolving workflow states, and performing minimal error handling. The patterns assume a Bun/Node runtime and the official Linear SDK methods.

When to use it

  • When authenticating autopilot processes to call the Linear API securely
  • When receiving and validating Linear webhooks and routing events
  • When automating issue creation, updates, or lifecycle transitions
  • When attaching build artifacts, screenshots, or logs to issues
  • When posting automated comments or audit notes to issues

Best practices

  • Always verify webhook signatures using your Linear webhook secret
  • Cache team, state, and label IDs to minimize API calls and latency
  • Implement exponential backoff and retry policies for rate limits
  • Log state transitions and webhook processing results for auditing
  • Gracefully handle webhook delivery failures and duplicate events

Example use cases

  • Create an issue for every failed CI run, attach logs, and comment with triage details
  • Move issues through workflow states automatically when external tasks complete
  • Consume Linear webhooks to trigger notifications or downstream automations
  • Bulk-query the autopilot assignment queue to balance workload among bots
  • Upload proof artifacts (screenshots, test reports) and attach them to issues

FAQ

How do I verify that webhooks are from Linear?

Compute an HMAC SHA-256 of the raw request body using your webhook secret and compare it to the Linear-Signature header; reject mismatches with 401.

How should I find the target workflow state before transitioning an issue?

Fetch the issue's team, then list the team's states and match by name to obtain the state ID before calling updateIssue.