Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create your first Linear issue and query using the SDK and GraphQL API. Use when making initial API calls, testing connection, or learning basic Linear CRUD operations. Trigger: "linear hello world", "first linear issue", "create linear issue", "linear API example", "test linear".
.claude/skills/jeremylongshore-linear-hello-world/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 11% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 73% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 70% | 0% |
Create your first issue, query teams, and explore the Linear data model using the @linear/sdk. Linear's API is GraphQL-based -- the SDK wraps it with typed models, lazy-loaded relations, and pagination helpers.
@linear/sdk installed (npm install @linear/sdk)LINEAR_API_KEY environment variable set (starts with lin_api_)typescriptimport { LinearClient } from "@linear/sdk"; const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! }); // Get current authenticated user const me = await client.viewer; console.log(`Hello, ${me.name}! (${me.email})`); // Get your organization const org = await me.organization; console.log(`Workspace: ${org.name}`);
Every issue in Linear belongs to a team. Teams have a short key (e.g., "ENG") used in identifiers like ENG-123.
typescriptconst teams = await client.teams(); console.log("Your teams:"); for (const team of teams.nodes) { console.log(` ${team.key} — ${team.name} (${team.id})`); }
typescriptconst team = teams.nodes[0]; const result = await client.createIssue({ teamId: team.id, title: "Hello from Linear SDK!", description: "This issue was created using the `@linear/sdk` TypeScript SDK.", priority: 3, // 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}`); }
typescript// Get recent issues from a team const issues = await client.issues({ filter: { team: { key: { eq: team.key } }, state: { type: { nin: ["completed", "canceled"] } }, }, first: 10, }); console.log(`\nOpen issues in ${team.key}:`); for (const issue of issues.nodes) { const state = await issue.state; console.log(` ${issue.identifier}: ${issue.title} [${state?.name}]`); }
Each team has customizable workflow states organized by type: triage, backlog, unstarted, started, completed, canceled.
typescriptconst states = await team.states(); console.log(`\nWorkflow states for ${team.key}:`); for (const state of states.nodes) { console.log(` ${state.name} (type: ${state.type}, position: ${state.position})`); }
typescript// Search for a specific issue by its human-readable identifier const searchResults = await client.issueSearch("ENG-1"); const found = searchResults.nodes[0]; if (found) { console.log(`\nFound: ${found.identifier}`); console.log(` Title: ${found.title}`); console.log(` Priority: ${found.priority}`); console.log(` Created: ${found.createdAt}`); const assignee = await found.assignee; console.log(` Assignee: ${assignee?.name ?? "Unassigned"}`); }
The SDK exposes the underlying GraphQL client for custom queries.
typescriptconst response = await client.client.rawRequest(` query TeamDashboard($teamKey: String!) { teams(filter: { key: { eq: $teamKey } }) { nodes { name key issues(first: 5, orderBy: updatedAt) { nodes { identifier title priority state { name type } assignee { name } } } } } } `, { teamKey: "ENG" }); console.log(JSON.stringify(response.data, null, 2));
| Error | Cause | Solution | |-------|-------|----------| | Authentication required | Invalid API key | Regenerate at Settings > Account > API | | Entity not found | Invalid ID or no access | Use client.teams() first to get valid IDs | | Validation error | Missing required field | teamId and title are required for createIssue | | Cannot read properties of null | Accessing nullable relation | Use optional chaining: (await issue.assignee)?.name |
typescriptimport { LinearClient } from "@linear/sdk"; async function main() { const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! }); const me = await client.viewer; console.log(`Connected as ${me.name}\n`); const teams = await client.teams(); const team = teams.nodes[0]; // Create issue const result = await client.createIssue({ teamId: team.id, title: "Hello from Linear SDK!", description: "Testing the API integration.", priority: 3, }); if (result.success) { const issue = await result.issue; console.log(`Created: ${issue?.identifier} — ${issue?.url}`); // Read it back const fetched = await client.issue(issue!.id); console.log(`Verified: ${fetched.title}`); // Clean up await fetched.delete(); console.log("Deleted test issue."); } } main().catch(console.error);
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 19,656 | 14,432 | -27% | 1 | 1 | 0% | 2,994 | 3,311 | +11% | 0 | 0 | — |
case-02 | pass→pass | 16,746 | 9,735 | -42% | 1 | 1 | 0% | 1,339 | 2,310 | +73% | 0 | 0 | — |
case-03 | pass→pass | 16,703 | 15,139 | -9% | 1 | 1 | 0% | 1,979 | 3,366 | +70% | 0 | 0 | — |
case-04 | pass→pass | 12,383 | 7,577 | -39% | 1 | 1 | 0% | 1,330 | 1,965 | +48% | 0 | 0 | — |
case-05 | pass→pass | 9,263 | 8,344 | -10% | 1 | 1 | 0% | 772 | 1,976 | +156% | 0 | 0 | — |
case-06 | fail→pass | 15,848 | 10,641 | -33% | 1 | 1 | 0% | 1,926 | 2,541 | +32% | 0 | 0 | — |
case-07 | pass→pass | 12,762 | 10,065 | -21% | 1 | 1 | 0% | 1,437 | 2,501 | +74% | 0 | 0 | — |
case-12 | pass→pass | 14,690 | 8,785 | -40% | 1 | 1 | 0% | 1,399 | 3,154 | +125% | 0 | 0 | — |
case-08 | pass→pass | 12,563 | 11,622 | -7% | 1 | 1 | 0% | 1,442 | 2,459 | +71% | 0 | 0 | — |
case-09 | fail→fail | 16,884 | 13,768 | -18% | 1 | 1 | 0% | 1,672 | 3,299 | +97% | 0 | 0 | — |
case-10 | pass→pass | 17,985 | 15,867 | -12% | 1 | 1 | 0% | 1,979 | 3,538 | +79% | 0 | 0 | — |
case-11 | pass→pass | 19,571 | 17,695 | -10% | 1 | 1 | 0% | 2,056 | 3,747 | +82% | 0 | 0 | — |
case-13 | pass→pass | 11,188 | 11,529 | +3% | 1 | 1 | 0% | 873 | 2,013 | +131% | 0 | 0 | — |
case-14 | pass→pass | 7,333 | 3,762 | -49% | 1 | 1 | 0% | 1,031 | 2,151 | +109% | 0 | 0 | — |
case-15 | pass→pass | 12,181 | 8,388 | -31% | 1 | 1 | 0% | 1,238 | 2,098 | +69% | 0 | 0 | — |
case-16 | pass→pass | 9,709 | 7,263 | -25% | 1 | 1 | 0% | 1,856 | 2,865 | +54% | 0 | 0 | — |
case-17 | pass→pass | 14,568 | 8,046 | -45% | 1 | 1 | 0% | 1,675 | 1,892 | +13% | 0 | 0 | — |
case-18 | fail→pass | 10,304 | 13,054 | +27% | 1 | 1 | 0% | 1,781 | 3,479 | +95% | 0 | 0 | — |
case-19 | fail→fail | 13,098 | 14,511 | +11% | 1 | 1 | 0% | 2,172 | 2,966 | +37% | 0 | 0 | — |
case-20 | pass→pass | 16,148 | 2,999 | -81% | 1 | 1 | 0% | 2,045 | 2,016 | -1% | 0 | 0 | — |
case-21 | pass→pass | 9,294 | 10,717 | +15% | 1 | 1 | 0% | 1,577 | 2,336 | +48% | 0 | 0 | — |
case-22 | pass→pass | 17,031 | 14,292 | -16% | 1 | 1 | 0% | 2,347 | 3,395 | +45% | 0 | 0 | — |
case-23 | pass→pass | 13,613 | 19,276 | +42% | 1 | 1 | 0% | 2,130 | 3,878 | +82% | 0 | 0 | — |
case-24 | pass→pass | 11,265 | 10,000 | -11% | 1 | 1 | 0% | 2,140 | 3,579 | +67% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.