agentsclimarketplace

Linear deploy integration

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/linear-pack/skills/linear-deploy-integration

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill linear-deploy-integration

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Deploy Linear-integrated applications and track deployments.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.2 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it

Linear Deploy Integration

Overview

Deploy Linear-integrated applications with automatic deployment tracking. Linear's GitHub integration links PRs to issues using magic words (Fixes, Closes, Resolves) and auto-detects Vercel preview links. This skill adds custom deployment comments, state transitions, and rollback tracking.

Prerequisites

  • Working Linear integration with API key or OAuth
  • Deployment platform (Vercel, Railway, Cloud Run, etc.)
  • GitHub integration enabled in Linear (Settings > Integrations > GitHub)

Instructions

Step 1: Deployment Workflow with Linear Tracking

# .github/workflows/deploy.yml
name: Deploy and Track

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Full history for commit scanning

      - name: Deploy
        id: deploy
        run: |
          # Replace with your deploy command
          DEPLOY_URL=$(npx vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tail -1)
          echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT

      - name: Track deployment in Linear
        if: success()
        env:
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
        run: |
          npx tsx scripts/track-deployment.ts \
            --env production \
            --url "${{ steps.deploy.outputs.url }}" \
            --sha "${{ github.sha }}" \
            --before "${{ github.event.before }}"

      - name: Create failure issue
        if: 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($i: IssueCreateInput!) { issueCreate(input: $i) { success } }",
              "variables": { "i": { "teamId": "${{ vars.LINEAR_TEAM_ID }}", "title": "[Deploy] Failed: ${{ github.sha }}", "priority": 1 } }
            }'

Step 2: Deployment Tracking Script

// scripts/track-deployment.ts
import { LinearClient } from "@linear/sdk";
import { execSync } from "child_process";
import { parseArgs } from "util";

const { values } = parseArgs({
  options: {
    env: { type: "string" },
    url: { type: "string" },
    sha: { type: "string" },
    before: { type: "string" },
  },
});

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

  // Extract Linear issue IDs from commit messages since last deploy
  const log = execSync(
    `git log --oneline ${values.before}..${values.sha} 2>/dev/null || echo ""`
  ).toString();

  const issueIds = [...new Set(log.match(/[A-Z]+-\d+/g) ?? [])];
  console.log(`Found ${issueIds.length} Linear issues in commits: ${issueIds.join(", ")}`);

  for (const identifier of issueIds) {
    const results = await client.issueSearch(identifier);
    const issue = results.nodes.find(i => i.identifier === identifier);
    if (!issue) continue;

    // Add deployment comment
    await client.createComment({
      issueId: issue.id,
      body: `Deployed to **${values.env}**: ${values.url}\n\nCommit: \`${values.sha?.substring(0, 7)}\``,
    });

    // Auto-transition based on environment
    const team = await issue.team;
    const states = await team!.states();

    if (values.env === "staging") {
      const reviewState = states.nodes.find(s =>
        s.name.toLowerCase().includes("review")
      );
      if (reviewState) await issue.update({ stateId: reviewState.id });
    } else if (values.env === "production") {
      const doneState = states.nodes.find(s => s.type === "completed");
      if (doneState) await issue.update({ stateId: doneState.id });
    }

    console.log(`Updated ${identifier} — deployed to ${values.env}`);
  }
}

trackDeployment().catch(console.error);

Step 3: Rollback Tracking

async function trackRollback(
  client: LinearClient,
  issueIdentifier: string,
  reason: string
) {
  const results = await client.issueSearch(issueIdentifier);
  const issue = results.nodes[0];
  if (!issue) return;

  // Add rollback comment
  await client.createComment({
    issueId: issue.id,
    body: `**Rolled back from production**\n\nReason: ${reason}\n\nIssue moved back to In Progress.`,
  });

  // Move back to In Progress with elevated priority
  const team = await issue.team;
  const states = await team!.states();
  const inProgress = states.nodes.find(s =>
    s.name.toLowerCase() === "in progress"
  );
  if (inProgress) {
    await issue.update({ stateId: inProgress.id, priority: 1 });
  }
}

Step 4: PR Template for Deployment Tracking

<!-- .github/PULL_REQUEST_TEMPLATE.md -->
## Linear Issues
<!-- Linear auto-links when you use magic words -->
Fixes ENG-XXX

## Deployment Notes
- [ ] Requires database migration
- [ ] Requires environment variable changes
- [ ] Requires Linear webhook reconfiguration
- [ ] Requires secret rotation

Step 5: Deployment Dashboard Query

// Query recently deployed issues
async function getDeploymentSummary(client: LinearClient, days = 14) {
  const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();

  const completed = await client.issues({
    filter: {
      state: { type: { eq: "completed" } },
      completedAt: { gte: since },
    },
    first: 100,
    orderBy: "completedAt",
  });

  console.log(`${completed.nodes.length} issues completed in last ${days} days:`);
  for (const issue of completed.nodes) {
    const assignee = await issue.assignee;
    console.log(`  ${issue.identifier}: ${issue.title} (${assignee?.name ?? "unassigned"})`);
  }

  return completed.nodes;
}

Error Handling

ErrorCauseSolution
LINEAR_API_KEY not setMissing CI secretAdd to GitHub repo Settings > Secrets
Issue not foundWrong workspace or deletedVerify team key matches workspace
Preview links not appearingGitHub integration offEnable in Linear Settings > Integrations > GitHub
Deploy comment missingIssue ID not in commitsFollow branch naming: feature/ENG-123-desc

Examples

Multi-Environment Matrix

strategy:
  matrix:
    include:
      - env: staging
        trigger: pull_request
      - env: production
        trigger: push

Resources

What ships with it: 2 files

11.8 KB alongside SKILL.md

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.