LI

linear-local-dev-loop

Quickly scaffold your local Linear development environment, including webhook testing and API integration.

Install

mkdir -p .claude/skills/linear-local-dev-loop && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/7619" && unzip -o skill.zip -d .claude/skills/linear-local-dev-loop && rm skill.zip

Installs to .claude/skills/linear-local-dev-loop

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.

Set up local Linear development environment and testing workflow.
65 charsno explicit “when” trigger
Intermediate

Key capabilities

  • Scaffold Node.js integration projects
  • Configure environment variables for Linear API
  • Tunnel webhooks locally with ngrok
  • Execute integration tests with vitest
  • Cleanup test data automatically

How it works

The skill provides a boilerplate structure for Linear integrations, including client initialization, test utilities for managing issues, and a webhook receiver for local event handling.

Inputs & outputs

You give it
Linear API key and webhook secret
You get back
Local development environment with webhook tunneling

When to use linear-local-dev-loop

  • Scaffolding a new Linear integration project
  • Configuring environment variables for Linear API
  • Testing webhooks locally with ngrok
  • Setting up integration testing

About this skill

Linear Local Dev Loop

Overview

Set up an efficient local development workflow for building Linear integrations. Covers project scaffolding, environment config, test utilities, webhook tunneling with ngrok, and integration testing with vitest.

Prerequisites

  • Node.js 18+ with TypeScript
  • @linear/sdk package
  • Separate Linear workspace or team for development (recommended)
  • ngrok or cloudflared for webhook tunnel testing

Instructions

Step 1: Project Scaffolding

set -euo pipefail
mkdir linear-integration && cd linear-integration
npm init -y
npm install @linear/sdk dotenv
npm install -D typescript @types/node vitest tsx

# TypeScript config
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --strict

Step 2: Environment Configuration

# .env (never commit)
cat > .env << 'EOF'
LINEAR_API_KEY=lin_api_dev_xxxxxxxxxxxx
LINEAR_WEBHOOK_SECRET=whsec_dev_xxxxxxxxxxxx
LINEAR_DEV_TEAM_KEY=DEV
NODE_ENV=development
EOF

# .env.example (commit this for onboarding)
cat > .env.example << 'EOF'
LINEAR_API_KEY=lin_api_your_key_here
LINEAR_WEBHOOK_SECRET=
LINEAR_DEV_TEAM_KEY=DEV
NODE_ENV=development
EOF

echo -e ".env\n.env.local\n.env.*.local" >> .gitignore

Step 3: Client Module with Connection Verification

// src/client.ts
import { LinearClient } from "@linear/sdk";
import "dotenv/config";

let _client: LinearClient | null = null;

export function getClient(): LinearClient {
  if (!_client) {
    const apiKey = process.env.LINEAR_API_KEY;
    if (!apiKey) throw new Error("LINEAR_API_KEY not set — copy .env.example to .env");
    _client = new LinearClient({ apiKey });
  }
  return _client;
}

export async function verifyConnection(): Promise<void> {
  const client = getClient();
  const viewer = await client.viewer;
  const teams = await client.teams();
  console.log(`[Linear] Connected as ${viewer.name} (${viewer.email})`);
  console.log(`[Linear] Teams: ${teams.nodes.map(t => t.key).join(", ")}`);
}

Step 4: Test Data Utilities

// src/test-utils.ts
import { getClient } from "./client";

const TEST_PREFIX = "[DEV-TEST]";

export async function getDevTeam() {
  const client = getClient();
  const teamKey = process.env.LINEAR_DEV_TEAM_KEY ?? "DEV";
  const teams = await client.teams({ filter: { key: { eq: teamKey } } });
  const team = teams.nodes[0];
  if (!team) throw new Error(`Team ${teamKey} not found — set LINEAR_DEV_TEAM_KEY`);
  return team;
}

export async function createTestIssue(title?: string) {
  const client = getClient();
  const team = await getDevTeam();
  const result = await client.createIssue({
    teamId: team.id,
    title: `${TEST_PREFIX} ${title ?? new Date().toISOString()}`,
    description: "Automated test issue — safe to delete",
    priority: 4, // Low
  });
  return result.issue;
}

export async function cleanupTestIssues() {
  const client = getClient();
  const team = await getDevTeam();
  const issues = await client.issues({
    filter: {
      team: { id: { eq: team.id } },
      title: { startsWith: TEST_PREFIX },
    },
    first: 100,
  });

  let deleted = 0;
  for (const issue of issues.nodes) {
    await issue.delete();
    deleted++;
  }
  console.log(`Cleaned up ${deleted} test issues`);
}

Step 5: Integration Tests with Vitest

// tests/linear.integration.test.ts
import { describe, it, expect, afterAll } from "vitest";
import { getClient } from "../src/client";
import { createTestIssue, cleanupTestIssues, getDevTeam } from "../src/test-utils";

describe("Linear Integration", () => {
  afterAll(async () => {
    await cleanupTestIssues();
  });

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

  it("creates and updates an issue", async () => {
    const client = getClient();
    const issue = await createTestIssue("vitest create");
    expect(issue).toBeDefined();
    expect(issue?.title).toContain("[DEV-TEST]");

    // Update it
    await client.updateIssue(issue!.id, { priority: 2 });
    const updated = await client.issue(issue!.id);
    expect(updated.priority).toBe(2);
  });

  it("queries workflow states", async () => {
    const team = await getDevTeam();
    const states = await team.states();
    const types = states.nodes.map(s => s.type);
    expect(types).toContain("unstarted");
    expect(types).toContain("completed");
  });
});

Step 6: Package Scripts

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "verify": "tsx src/verify-connection.ts",
    "test": "vitest run",
    "test:watch": "vitest --watch",
    "cleanup": "tsx src/cleanup.ts"
  }
}

Step 7: Webhook Local Development with ngrok

# Terminal 1: Start your webhook server
npm run dev

# Terminal 2: Expose port 3000 via ngrok
ngrok http 3000
# Copy the https://xxxx.ngrok-free.app URL

# Register webhook in Linear:
# Settings > API > Webhooks > New webhook
# URL: https://xxxx.ngrok-free.app/webhooks/linear
# Select: Issues, Comments

Minimal webhook receiver for local testing:

// src/webhook-dev.ts
import express from "express";
import crypto from "crypto";

const app = express();
app.post("/webhooks/linear", express.raw({ type: "*/*" }), (req, res) => {
  const body = req.body.toString();
  const sig = req.headers["linear-signature"] as string;
  const secret = process.env.LINEAR_WEBHOOK_SECRET!;

  if (secret && sig) {
    const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
    if (sig !== expected) {
      console.warn("Signature mismatch — check LINEAR_WEBHOOK_SECRET");
    }
  }

  const event = JSON.parse(body);
  console.log(`[Webhook] ${event.type}.${event.action}:`, event.data?.identifier ?? event.data?.id);
  res.json({ ok: true });
});

app.listen(3000, () => console.log("Webhook server on http://localhost:3000"));

Error Handling

ErrorCauseSolution
LINEAR_API_KEY not setMissing .envCopy .env.example to .env and fill in values
Team DEV not foundWrong team keySet LINEAR_DEV_TEAM_KEY to a valid team key
Cannot find moduleTypeScript path issueCheck tsconfig.json module resolution
Webhook not receivedTunnel not runningStart ngrok http 3000 and register the URL
Authentication requiredExpired dev API keyRegenerate in Linear Settings > Account > API

Examples

Quick Connection Test Script

// src/verify-connection.ts
import { verifyConnection } from "./client";
verifyConnection()
  .then(() => console.log("Connection OK"))
  .catch((err) => { console.error("Connection FAILED:", err.message); process.exit(1); });

Resources

Prerequisites

Node.js 18+ with TypeScript@linear/sdk packagengrok or cloudflared

Limitations

  • Requires manual registration of webhook URLs in Linear settings

How it compares

It automates the boilerplate setup and local webhook testing process that would otherwise require manual configuration of SDK clients and tunnel services.

Compared to similar skills

linear-local-dev-loop side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
linear-local-dev-loop (this skill)127dReviewIntermediate
dependency-upgrade265moReviewIntermediate
chrome-devtools417moReviewIntermediate
playwright-browser-automation297moReviewIntermediate

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

You might also like

dependency-upgrade

wshobson

Manage major dependency version upgrades with compatibility analysis, staged rollout, and comprehensive testing. Use when upgrading framework versions, updating major dependencies, or managing breaking changes in libraries.

26240

chrome-devtools

mrgoonie

Browser automation, debugging, and performance analysis using Puppeteer CLI scripts. Use for automating browsers, taking screenshots, analyzing performance, monitoring network traffic, web scraping, form automation, and JavaScript debugging.

41157

playwright-browser-automation

lackeyjb

Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.

29146

nestjs-expert

davila7

Nest.js framework expert specializing in module architecture, dependency injection, middleware, guards, interceptors, testing with Jest/Supertest, TypeORM/Mongoose integration, and Passport.js authentication. Use PROACTIVELY for any Nest.js application issues including architecture decisions, testing strategies, performance optimization, or debugging complex dependency injection problems. If a specialized expert is a better fit, I will recommend switching and stop.

3758

zod-4

prowler-cloud

Zod 4 schema validation patterns. Trigger: When creating or updating Zod v4 schemas for validation/parsing (forms, request payloads, adapters), including v3 -> v4 migration patterns.

1260

develop-ai-functions-example

vercel

Develop examples for AI SDK functions. Use when creating, running, or modifying examples under examples/ai-functions/src to validate provider support, demonstrate features, or create test fixtures.

536

Search skills

Search the agent skills registry