home / skills / jeremylongshore / claude-code-plugins-plus-skills / linear-ci-integration

This skill helps you set up Linear CI with GitHub Actions, automating tests and ensuring issue tracking from CI pipelines.

npx playbooks add skill jeremylongshore/claude-code-plugins-plus-skills --skill linear-ci-integration

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

Files (1)
SKILL.md
7.4 KB
---
name: linear-ci-integration
description: |
  Configure Linear CI/CD integration with GitHub Actions and testing.
  Use when setting up automated testing, configuring CI pipelines,
  or integrating Linear sync into your build process.
  Trigger with phrases like "linear CI", "linear GitHub Actions",
  "linear automated tests", "CI linear pipeline", "linear CI/CD".
allowed-tools: Read, Write, Edit, Bash(gh:*)
version: 1.0.0
license: MIT
author: Jeremy Longshore <[email protected]>
---

# Linear CI Integration

## Overview
Integrate Linear into your CI/CD pipeline for automated testing and deployment tracking.

## Prerequisites
- GitHub repository with Actions enabled
- Linear API key for CI
- npm/pnpm project configured

## Instructions

### Step 1: Store Secrets in GitHub
```bash
# Using GitHub CLI
gh secret set LINEAR_API_KEY --body "lin_api_xxxxxxxxxxxx"
gh secret set LINEAR_WEBHOOK_SECRET --body "your_webhook_secret"

# Or use GitHub web UI:
# Settings > Secrets and variables > Actions > New repository secret
```

### Step 2: Create Test Workflow
```yaml
# .github/workflows/linear-integration.yml
name: Linear Integration Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run Linear integration tests
        run: npm run test:linear
        env:
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: test-results/
```

### Step 3: Create Integration Test Suite
```typescript
// tests/linear.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { LinearClient } from "@linear/sdk";

describe("Linear Integration", () => {
  let client: LinearClient;
  let testTeamId: string;
  const createdIssueIds: string[] = [];

  beforeAll(async () => {
    const apiKey = process.env.LINEAR_API_KEY;
    if (!apiKey) {
      throw new Error("LINEAR_API_KEY required for integration tests");
    }

    client = new LinearClient({ apiKey });

    // Get test team
    const teams = await client.teams();
    testTeamId = teams.nodes[0].id;
  });

  afterAll(async () => {
    // Cleanup test issues
    for (const id of createdIssueIds) {
      try {
        await client.deleteIssue(id);
      } catch {
        // Ignore cleanup errors
      }
    }
  });

  it("should authenticate successfully", async () => {
    const viewer = await client.viewer;
    expect(viewer.name).toBeDefined();
    expect(viewer.email).toBeDefined();
  });

  it("should create an issue", async () => {
    const result = await client.createIssue({
      teamId: testTeamId,
      title: `[CI Test] ${new Date().toISOString()}`,
      description: "Created by CI pipeline",
    });

    expect(result.success).toBe(true);
    const issue = await result.issue;
    expect(issue?.identifier).toBeDefined();

    if (issue) createdIssueIds.push(issue.id);
  });

  it("should query issues", async () => {
    const issues = await client.issues({ first: 10 });
    expect(issues.nodes.length).toBeGreaterThan(0);
  });
});
```

### Step 4: PR Status Updates
```yaml
# .github/workflows/linear-pr-status.yml
name: Update Linear Issues from PR

on:
  pull_request:
    types: [opened, closed, merged]

jobs:
  update-linear:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Extract Linear Issue ID
        id: extract
        run: |
          # Extract issue identifier from branch name (e.g., feature/ENG-123-description)
          BRANCH_NAME="${{ github.head_ref }}"
          ISSUE_ID=$(echo "$BRANCH_NAME" | grep -oE '[A-Z]+-[0-9]+' | head -1)
          echo "issue_id=$ISSUE_ID" >> $GITHUB_OUTPUT

      - name: Update Linear Issue
        if: steps.extract.outputs.issue_id
        run: |
          npx ts-node scripts/update-linear-from-pr.ts \
            --issue "${{ steps.extract.outputs.issue_id }}" \
            --pr "${{ github.event.pull_request.number }}" \
            --action "${{ github.event.action }}"
        env:
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
```

### Step 5: Update Script
```typescript
// scripts/update-linear-from-pr.ts
import { LinearClient } from "@linear/sdk";
import { parseArgs } from "util";

const { values } = parseArgs({
  options: {
    issue: { type: "string" },
    pr: { type: "string" },
    action: { type: "string" },
  },
});

async function main() {
  const client = new LinearClient({
    apiKey: process.env.LINEAR_API_KEY!,
  });

  const issue = await client.issue(values.issue!);

  // Add PR link to issue
  await client.createComment({
    issueId: issue.id,
    body: `PR #${values.pr} ${values.action}: https://github.com/${process.env.GITHUB_REPOSITORY}/pull/${values.pr}`,
  });

  // Update issue state based on PR action
  if (values.action === "opened") {
    // Move to "In Review" state
    const team = await issue.team;
    const states = await team?.states();
    const reviewState = states?.nodes.find(s =>
      s.name.toLowerCase().includes("review")
    );
    if (reviewState) {
      await client.updateIssue(issue.id, { stateId: reviewState.id });
    }
  } else if (values.action === "closed" || values.action === "merged") {
    // Move to "Done" state
    const team = await issue.team;
    const states = await team?.states();
    const doneState = states?.nodes.find(s => s.type === "completed");
    if (doneState) {
      await client.updateIssue(issue.id, { stateId: doneState.id });
    }
  }
}

main().catch(console.error);
```

### Step 6: Create Issues from CI Failures
```yaml
# .github/workflows/create-issue-on-failure.yml
name: Create Linear Issue on Test Failure

on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]

jobs:
  create-issue:
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    runs-on: ubuntu-latest
    steps:
      - name: Create Linear Issue
        run: |
          curl -X POST https://api.linear.app/graphql \
            -H "Authorization: ${{ secrets.LINEAR_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "query": "mutation { issueCreate(input: { teamId: \"${{ vars.LINEAR_TEAM_ID }}\", title: \"[CI] Build failure: ${{ github.event.workflow_run.head_branch }}\", description: \"Build failed: ${{ github.event.workflow_run.html_url }}\", priority: 1 }) { success } }"
            }'
```

## Output
- Automated test pipeline
- PR-to-issue linking
- Automatic state transitions
- Failure issue creation
- Test result artifacts

## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| `Secret not found` | Missing GitHub secret | Add LINEAR_API_KEY to repository secrets |
| `Issue not found` | Invalid issue identifier | Verify branch naming convention |
| `Permission denied` | Insufficient API key scope | Regenerate API key with write access |

## Resources
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Linear GitHub Integration](https://linear.app/docs/github)
- [Linear API Authentication](https://developers.linear.app/docs/graphql/authentication)

## Next Steps
Configure deployment integration with `linear-deploy-integration`.

Overview

This skill configures Linear CI/CD integration with GitHub Actions to run automated tests, update Linear issues from PRs, and create issues on failures. It streamlines test runs, PR-to-issue linking, and automatic issue state transitions so your team can trace build and review status inside Linear. The setup uses GitHub secrets, Node.js workflows, and small TypeScript scripts to call the Linear GraphQL API.

How this skill works

The skill adds GitHub Actions workflows that install dependencies, run a Linear-focused test suite, and upload test artifacts. It includes a workflow to extract an issue identifier from branch names and call a TypeScript script that comments on and transitions Linear issues according to PR events. A separate workflow can create Linear issues automatically when CI runs fail by calling the Linear GraphQL API.

When to use it

  • Adding automated Linear integration to existing GitHub Actions CI pipelines
  • Running end-to-end integration tests that require a Linear API key
  • Automatically linking PRs to Linear issues and transitioning states
  • Creating Linear issues from CI failures to track build problems
  • Centralizing PR status, test results, and issue lifecycle in Linear

Best practices

  • Store LINEAR_API_KEY and LINEAR_WEBHOOK_SECRET in GitHub repository secrets
  • Use a dedicated test team and cleanup created test issues after runs
  • Enforce branch naming conventions that include the Linear issue ID (e.g., ENG-123)
  • Run integration tests only on protected branches or CI runners to avoid spamming Linear
  • Limit API key scope to required permissions and rotate keys periodically

Example use cases

  • Run integration tests that create and delete issues to validate Linear API access
  • Automatically move an issue to "In Review" when a PR opens and to "Done" when merged
  • Upload test result artifacts for QA review after each CI run
  • Create a Linear issue when a nightly build or CI workflow fails
  • Annotate issues with PR links and actions for auditability

FAQ

What secrets are required?

Set LINEAR_API_KEY and optionally LINEAR_WEBHOOK_SECRET in GitHub Actions secrets.

How does the workflow find the Linear issue?

It extracts an issue identifier from the branch name using a regex like [A-Z]+-[0-9]+; follow that branch naming convention.

How do I avoid creating test noise in production Linear teams?

Use a dedicated test team or filter tests to run only on specific branches or runners, and ensure cleanup logic removes created test issues.