Claude Skills Guide

Claude Skills with Linear Project Management Tutorial

Linear is a project management tool built for engineering teams, known for its speed and clean keyboard-driven interface. Integrating Claude skills with Linear lets you automate issue triage, generate technical specifications, analyze sprint data, and write issue descriptions that actually help developers. This tutorial covers Claude skills with Linear project management from API setup to automated workflows.

What You Can Automate

Prerequisites

Step 1: Get Your Linear API Key

  1. Open Linear → Settings → API
  2. Under Personal API keys, create a new key with label “Claude Skills Bot”
  3. Copy the key — it starts with lin_api_

Step 2: Install Dependencies

mkdir claude-linear-bot && cd claude-linear-bot
npm install @linear/sdk @anthropic-ai/sdk dotenv

Create .env:

LINEAR_API_KEY=lin_api_your_key_here
ANTHROPIC_API_KEY=your_claude_api_key
LINEAR_TEAM_ID=your_team_id

Find your team ID from the Linear URL or API: https://linear.app/{workspace}/team/{team_id}/

Step 3: Initialize Clients

require('dotenv').config();
const { LinearClient } = require('@linear/sdk');
const Anthropic = require('@anthropic-ai/sdk');

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

Step 4: Fetch Issues and Run Claude Analysis

async function getRecentIssues(teamId, limit = 20) {
  const issues = await linear.issues({
    filter: { team: { id: { eq: teamId } } },
    orderBy: 'createdAt',
    first: limit,
  });
  
  return issues.nodes.map(issue => ({
    id: issue.id,
    identifier: issue.identifier,
    title: issue.title,
    description: issue.description || '',
    state: issue.state?.name,
    priority: issue.priority,
  }));
}

async function triageIssue(issue) {
  const prompt = `Issue: ${issue.identifier}${issue.title}

Description: ${issue.description || 'No description provided'}

As the TDD skill for Claude Code, analyze this bug report:
1. What tests would catch this bug?
2. What are the likely code areas affected?
3. Suggest concrete reproduction steps if missing
4. Estimate risk level: low/medium/high

Return JSON: { "test_suggestions": [], "affected_areas": [], "repro_steps": [], "risk_level": "", "triage_notes": "" }`;

  const message = await claude.messages.create({
    model: 'claude-opus-4-6',
    max_tokens: 1024,
    system: `You are the TDD skill for Claude Code. Analyze issues for test coverage implications and technical risk.`,
    messages: [{ role: 'user', content: prompt }],
  });
  
  try {
    return JSON.parse(message.content[0].text);
  } catch {
    return { triage_notes: message.content[0].text };
  }
}

Step 5: Update Linear Issues with Claude Analysis

async function addTriageComment(issueId, analysis) {
  const commentBody = `## Claude Triage Analysis

**Risk Level:** ${analysis.risk_level || 'Unknown'}

### Suggested Tests
${(analysis.test_suggestions || []).map(t => `- ${t}`).join('\n')}

### Affected Code Areas
${(analysis.affected_areas || []).map(a => `- ${a}`).join('\n')}

### Reproduction Steps
${(analysis.repro_steps || []).map((s, i) => `${i + 1}. ${s}`).join('\n')}

### Notes
${analysis.triage_notes || ''}

*Generated by Claude TDD Skill*`;

  await linear.createComment({
    issueId,
    body: commentBody,
  });
}

Step 6: Generate Technical Specs from Feature Requests

async function generateTechSpec(featureRequest) {
  const message = await claude.messages.create({
    model: 'claude-opus-4-6',
    max_tokens: 2048,
    system: `You are a senior software architect. Generate clear, actionable technical specifications for feature requests. Include implementation approach, API changes, database changes, and testing strategy.`,
    messages: [{
      role: 'user',
      content: `Generate a technical spec for this feature request:\n\n${featureRequest}`,
    }],
  });
  
  return message.content[0].text;
}

async function createSpecIssue(teamId, title, spec) {
  const states = await linear.workflowStates({
    filter: { team: { id: { eq: teamId } } },
  });
  const backlogState = states.nodes.find(s => s.name === 'Backlog' || s.name === 'Todo');
  
  await linear.createIssue({
    teamId,
    title: `[Tech Spec] ${title}`,
    description: spec,
    stateId: backlogState?.id,
    labelIds: [], // add your "spec" label ID here
  });
}

Step 7: Sprint Summary with Supermemory Tracking

async function generateSprintSummary(teamId) {
  // Get issues completed in last 2 weeks
  const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
  
  const completedIssues = await linear.issues({
    filter: {
      team: { id: { eq: teamId } },
      completedAt: { gt: twoWeeksAgo },
    },
    first: 50,
  });
  
  const issueList = completedIssues.nodes
    .map(i => `- ${i.identifier}: ${i.title} (${i.estimate || '?'} pts)`)
    .join('\n');
  
  const message = await claude.messages.create({
    model: 'claude-opus-4-6',
    max_tokens: 1024,
    system: `You are the supermemory skill for Claude Code. Generate concise sprint summaries for stakeholders. Focus on impact, not tasks.`,
    messages: [{
      role: 'user',
      content: `Summarize this sprint's completed work for a stakeholder update:\n\n${issueList}`,
    }],
  });
  
  return message.content[0].text;
}

Step 8: Set Up a Webhook for Real-Time Triage

Linear supports webhooks for real-time events. Create an Express server to receive them:

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

app.post('/webhook/linear', async (req, res) => {
  // Verify Linear webhook signature
  const signature = req.headers['linear-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.LINEAR_WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex');
  
  if (signature !== expected) {
    return res.status(401).send('Invalid signature');
  }
  
  res.status(200).send('OK'); // Respond quickly
  
  const { action, type, data } = req.body;
  
  // Auto-triage new bug reports
  if (type === 'Issue' && action === 'create' && data.labelNames?.includes('bug')) {
    const analysis = await triageIssue(data);
    await addTriageComment(data.id, analysis);
    console.log(`Triaged: ${data.identifier}`);
  }
});

app.listen(3000, () => console.log('Linear webhook server running on :3000'));

Step 9: Batch Triage Existing Issues

Run this once to triage your backlog:

async function triageBacklog(teamId) {
  const issues = await getRecentIssues(teamId, 50);
  const bugs = issues.filter(i => i.state === 'Todo' || i.state === 'Backlog');
  
  for (const issue of bugs) {
    console.log(`Triaging ${issue.identifier}...`);
    const analysis = await triageIssue(issue);
    await addTriageComment(issue.id, analysis);
    // Respect rate limits
    await new Promise(r => setTimeout(r, 1000));
  }
  
  console.log(`Triaged ${bugs.length} issues`);
}

triageBacklog(process.env.LINEAR_TEAM_ID);

Conclusion

Claude skills with Linear project management transforms routine PM work into automated intelligence. The tdd skill makes triage actionable, supermemory tracks patterns across sprints, and spec generation saves hours per feature. Start with the webhook-based real-time triage and add batch processing for your existing backlog.


Built by theluckystrike — More at zovo.one