LI

linear-ci-integration

CI/CD integration for linking GitHub Actions workflows with Linear issue and cycle management.

Install

mkdir -p .claude/skills/linear-ci-integration && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/7610" && unzip -o skill.zip -d .claude/skills/linear-ci-integration && rm skill.zip

Installs to .claude/skills/linear-ci-integration

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Integrate Linear with GitHub Actions CI/CD pipelines.
53 charsno explicit “when” trigger
Intermediate

Key capabilities

  • Automate issue state transitions on pull request events
  • Link GitHub pull requests to Linear issues
  • Create Linear issues automatically from CI build failures
  • Run integration tests against the Linear API
  • Extract issue identifiers from branch names

How it works

The integration uses GitHub Actions to execute scripts that interact with the Linear API, linking PRs to issues and reacting to build outcomes.

Inputs & outputs

You give it
GitHub PR or CI workflow event
You get back
Linear issue update or creation

When to use linear-ci-integration

  • Automate issue state transitions on PRs
  • Create Linear issues from CI failures
  • Link GitHub pull requests to issues
  • Run integration tests against Linear API

About this skill

Linear CI Integration

Overview

Integrate Linear into GitHub Actions CI/CD pipelines: run integration tests against the Linear API, automatically link PRs to issues, transition issue states on PR events, and create Linear issues from build failures.

Prerequisites

  • GitHub repository with Actions enabled
  • Linear API key stored as GitHub secret
  • npm/pnpm project with @linear/sdk configured

Instructions

Step 1: Store Secrets in GitHub

# Using GitHub CLI
gh secret set LINEAR_API_KEY --body "lin_api_xxxxxxxxxxxx"
gh secret set LINEAR_WEBHOOK_SECRET --body "whsec_xxxxxxxxxxxx"

# Store team ID for CI-created issues
gh variable set LINEAR_TEAM_ID --body "team-uuid-here"

Step 2: Integration Test Workflow

# .github/workflows/linear-tests.yml
name: Linear Integration Tests

on:
  push:
    branches: [main]
  pull_request:

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run test:linear
        env:
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}

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

Step 3: Integration Test Suite

// 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 teamId: string;
  const cleanup: 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 });

    const teams = await client.teams();
    teamId = teams.nodes[0].id;
  });

  afterAll(async () => {
    for (const id of cleanup) {
      try { await client.deleteIssue(id); } catch {}
    }
  });

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

  it("creates an issue", async () => {
    const result = await client.createIssue({
      teamId,
      title: `[CI] ${new Date().toISOString()}`,
      description: "Created by CI pipeline",
    });
    expect(result.success).toBe(true);
    const issue = await result.issue;
    expect(issue?.identifier).toBeDefined();
    if (issue) cleanup.push(issue.id);
  });

  it("queries issues with filtering", async () => {
    const issues = await client.issues({
      first: 10,
      filter: { team: { id: { eq: teamId } } },
    });
    expect(issues.nodes.length).toBeGreaterThan(0);
  });

  it("lists workflow states", async () => {
    const teams = await client.teams();
    const states = await teams.nodes[0].states();
    expect(states.nodes.length).toBeGreaterThan(0);
    expect(states.nodes.some(s => s.type === "completed")).toBe(true);
  });
});

Step 4: PR-to-Issue Linking Workflow

Automatically update Linear issues when PRs are opened, merged, or closed. Extracts issue identifiers from branch names (e.g., feature/ENG-123-description).

# .github/workflows/linear-pr-sync.yml
name: Sync PR to Linear

on:
  pull_request:
    types: [opened, closed]

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci

      - name: Extract Linear issue ID from branch
        id: extract
        run: |
          BRANCH="${{ github.head_ref }}"
          ISSUE_ID=$(echo "$BRANCH" | grep -oE '[A-Z]+-[0-9]+' | head -1 || true)
          echo "issue_id=$ISSUE_ID" >> $GITHUB_OUTPUT

      - name: Update Linear issue
        if: steps.extract.outputs.issue_id
        env:
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
        run: |
          npx tsx scripts/sync-pr-to-linear.ts \
            --issue "${{ steps.extract.outputs.issue_id }}" \
            --pr "${{ github.event.pull_request.number }}" \
            --action "${{ github.event.action }}" \
            --merged "${{ github.event.pull_request.merged }}"

Step 5: PR Sync Script

// scripts/sync-pr-to-linear.ts
import { LinearClient } from "@linear/sdk";
import { parseArgs } from "util";

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

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

  // Find issue by identifier search
  const results = await client.issueSearch(values.issue!);
  const issue = results.nodes[0];
  if (!issue) {
    console.log(`Issue ${values.issue} not found — skipping`);
    return;
  }

  const prUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}/pull/${values.pr}`;

  // Add comment linking to PR
  await client.createComment({
    issueId: issue.id,
    body: `PR #${values.pr} ${values.action}: View PR`,
  });

  // Transition state based on PR action
  const team = await issue.team;
  const states = await team!.states();

  if (values.action === "opened") {
    const reviewState = states.nodes.find(s =>
      s.name.toLowerCase().includes("review") || s.name.toLowerCase().includes("in progress")
    );
    if (reviewState) await client.updateIssue(issue.id, { stateId: reviewState.id });
  } else if (values.action === "closed" && values.merged === "true") {
    const doneState = states.nodes.find(s => s.type === "completed");
    if (doneState) await client.updateIssue(issue.id, { stateId: doneState.id });
  }

  console.log(`Updated ${values.issue} for PR #${values.pr} (${values.action})`);
}

main().catch(console.error);

Step 6: Create Issue on CI Failure

# .github/workflows/issue-on-failure.yml
name: Create Linear Issue on 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 for build failure
        env:
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
        run: |
          curl -s -X POST https://api.linear.app/graphql \
            -H "Authorization: $LINEAR_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "query": "mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { identifier url } } }",
              "variables": {
                "input": {
                  "teamId": "${{ vars.LINEAR_TEAM_ID }}",
                  "title": "[CI] Build failure: ${{ github.event.workflow_run.head_branch }}",
                  "description": "Build failed on branch `${{ github.event.workflow_run.head_branch }}`.\n\nView run",
                  "priority": 1
                }
              }
            }'

Error Handling

ErrorCauseSolution
Secret not foundMissing GitHub secretAdd LINEAR_API_KEY to repo Settings > Secrets > Actions
Issue not foundWrong identifier or workspaceVerify branch naming convention matches team key
Permission deniedAPI key lacks write scopeRegenerate key with write access
Duplicate CI issuesFailure workflow runs repeatedlyAdd deduplication check before creating

Examples

PR Template for Linear Integration

<!-- .github/PULL_REQUEST_TEMPLATE.md -->
## Linear Issue
<!-- Use magic words: Fixes, Closes, Resolves -->
Fixes ENG-XXX

## Changes
-

## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass

Resources

When not to use it

  • When GitHub Actions are not enabled
  • When Linear API key lacks write scope

Prerequisites

GitHub repository with Actions enabledLinear API key stored as GitHub secretnpm/pnpm project with @linear/sdk configured

Limitations

  • Requires branch naming convention to match team key
  • API key must have write access

How it compares

It automates the synchronization between code changes and project management status, removing the need for manual issue updates.

Compared to similar skills

linear-ci-integration side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
linear-ci-integration (this skill)127dCautionIntermediate
regression02moReviewAdvanced
work-on-issue06moReviewIntermediate
ticket-implementation-copilot05moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

Search skills

Search the agent skills registry