Linear core workflow a
Skill jeremylongshore/claude-code-plugins-plus-skills/skills/.curated/linear-core-workflow-a
425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill linear-core-workflow-aAssembled 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
'Issue lifecycle management with Linear: create, update, transition,
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.9 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it
Linear Core Workflow A: Issue Lifecycle
Overview
Master issue lifecycle management: creating, updating, transitioning states, building parent/sub-issue hierarchies, managing labels, and commenting. Linear issues flow through typed workflow states (triage -> backlog -> unstarted -> started -> completed | canceled), belong to a team, and support priorities 0-4, estimates, due dates, labels, and cycle/project assignment.
Prerequisites
@linear/sdkinstalled with API key or OAuth token configured- Access to target team(s)
- Understanding of your team's workflow states
Instructions
Step 1: Create Issues
import { LinearClient } from "@linear/sdk";
const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! });
// Get team
const teams = await client.teams();
const team = teams.nodes.find(t => t.key === "ENG") ?? teams.nodes[0];
// Basic issue
const result = await client.createIssue({
teamId: team.id,
title: "Implement user authentication",
description: "Add OAuth 2.0 login flow with Google and GitHub providers.",
priority: 2, // 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low
});
if (result.success) {
const issue = await result.issue;
console.log(`Created: ${issue?.identifier} — ${issue?.title}`);
console.log(`URL: ${issue?.url}`);
}
// Issue with full metadata
const labelResult = await client.issueLabels({ filter: { name: { eq: "Bug" } } });
const bugLabel = labelResult.nodes[0];
const states = await team.states();
const todoState = states.nodes.find(s => s.type === "unstarted")!;
await client.createIssue({
teamId: team.id,
title: "Fix login redirect loop on Safari",
description: "Users get stuck in infinite redirect after SSO callback.",
priority: 1,
stateId: todoState.id,
assigneeId: "user-uuid",
labelIds: bugLabel ? [bugLabel.id] : [],
estimate: 3,
dueDate: "2026-04-15",
});
Step 2: Update Issues
// Update by issue ID
await client.updateIssue("issue-uuid", {
title: "Updated title",
priority: 1,
estimate: 5,
dueDate: "2026-04-30",
});
// Find issue by team key + number, then update
const issues = await client.issues({
filter: { number: { eq: 123 }, team: { key: { eq: "ENG" } } },
});
const issue = issues.nodes[0];
if (issue) {
await issue.update({
priority: 2,
description: "Updated description with more details.",
});
}
// Add/remove labels
const featureLabel = (await client.issueLabels({
filter: { name: { eq: "Feature" } },
})).nodes[0];
if (featureLabel) {
await client.updateIssue(issue.id, {
labelIds: [...(issue.labelIds ?? []), featureLabel.id],
});
}
Step 3: State Transitions
// List all workflow states for a team
const teamStates = await team.states();
for (const state of teamStates.nodes) {
console.log(`${state.name} (type: ${state.type}, position: ${state.position})`);
}
// Output example:
// Triage (type: triage, position: 0)
// Backlog (type: backlog, position: 1)
// Todo (type: unstarted, position: 2)
// In Progress (type: started, position: 3)
// In Review (type: started, position: 4)
// Done (type: completed, position: 5)
// Canceled (type: canceled, position: 6)
// Move to "In Progress"
const inProgress = teamStates.nodes.find(s => s.name === "In Progress");
if (inProgress) {
await client.updateIssue(issue.id, { stateId: inProgress.id });
}
// Complete an issue
const done = teamStates.nodes.find(s => s.type === "completed");
if (done) {
await issue.update({ stateId: done.id });
}
Step 4: Parent/Sub-Issue Hierarchy
// Create parent issue
const parentResult = await client.createIssue({
teamId: team.id,
title: "Auth system overhaul",
description: "Epic: modernize authentication infrastructure.",
});
const parent = await parentResult.issue;
// Create sub-issues under parent
await client.createIssue({
teamId: team.id,
title: "Implement JWT token refresh",
parentId: parent!.id,
priority: 2,
});
await client.createIssue({
teamId: team.id,
title: "Add MFA support",
parentId: parent!.id,
priority: 3,
});
// List sub-issues
const children = await parent!.children();
for (const child of children.nodes) {
console.log(` Sub: ${child.identifier} — ${child.title}`);
}
Step 5: Issue Relations
// Relation types: "blocks", "duplicate", "related"
await client.createIssueRelation({
issueId: "blocked-issue-id",
relatedIssueId: "blocking-issue-id",
type: "blocks",
});
// Mark as duplicate
await client.createIssueRelation({
issueId: "duplicate-issue-id",
relatedIssueId: "original-issue-id",
type: "duplicate",
});
// List relations on an issue
const relations = await issue.relations();
for (const rel of relations.nodes) {
const related = await rel.relatedIssue;
console.log(`${rel.type}: ${related?.identifier}`);
}
Step 6: Comments
// Add a comment (supports Markdown)
await client.createComment({
issueId: issue.id,
body: "Deployed fix to staging.\n\n```bash\nnpm run test:e2e -- --filter auth\n```\n\nAll 47 tests passing.",
});
// List comments on an issue
const comments = await issue.comments();
for (const comment of comments.nodes) {
const user = await comment.user;
console.log(`${user?.name}: ${comment.body.substring(0, 80)}...`);
}
Step 7: Attachments
// Create a link attachment on an issue
await client.createAttachment({
issueId: issue.id,
title: "Figma Design",
url: "https://figma.com/file/xxx",
subtitle: "Login page redesign",
});
Error Handling
| Error | Cause | Solution |
|---|---|---|
Entity not found | Invalid issue ID or deleted | Verify with client.issue(id) first |
State not found | Wrong team's state ID | List states for correct team: team.states() |
Validation error on create | Missing required field | teamId + title required; priority must be 0-4 |
Circular dependency | Issue blocks itself transitively | Validate relation graph before creating |
Forbidden | No write access to team | Check team membership and API key scope |
Examples
Bulk Create from CSV
import { parse } from "csv-parse/sync";
import fs from "fs";
const rows = parse(fs.readFileSync("issues.csv"), { columns: true });
for (const row of rows) {
const result = await client.createIssue({
teamId: team.id,
title: row.title,
description: row.description,
priority: parseInt(row.priority) || 3,
});
const issue = await result.issue;
console.log(`Created: ${issue?.identifier}`);
}
Close Stale Issues
const stale = await client.issues({
filter: {
state: { type: { in: ["unstarted", "started"] } },
updatedAt: { lt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString() },
},
first: 50,
});
const canceled = (await team.states()).nodes.find(s => s.type === "canceled")!;
for (const issue of stale.nodes) {
await issue.update({ stateId: canceled.id });
await client.createComment({
issueId: issue.id,
body: "Auto-closed: no activity for 90 days.",
});
console.log(`Closed: ${issue.identifier} (last updated ${issue.updatedAt})`);
}