Claude-code-plugins-plus linear-common-errors
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/linear-pack/skills/linear-common-errors" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-linear-common-errors && rm -rf "$T"
manifest:
plugins/saas-packs/linear-pack/skills/linear-common-errors/SKILL.mdsource content
Linear Common Errors
Overview
Quick reference for diagnosing and resolving common Linear API and SDK errors. Linear's GraphQL API returns errors in
response.errors[] with extensions.type and extensions.userPresentableMessage fields. HTTP 200 responses can still contain partial errors -- always check the errors array.
Prerequisites
- Linear SDK or raw API access configured
- Access to application logs
- Understanding of GraphQL error response format
Instructions
Error Response Structure
// Linear GraphQL error shape interface LinearGraphQLResponse { data: Record<string, any> | null; errors?: Array<{ message: string; path?: string[]; extensions: { type: string; // "authentication_error", "forbidden", "ratelimited", etc. userPresentableMessage?: string; }; }>; } // SDK throws these typed errors import { LinearError, InvalidInputLinearError } from "@linear/sdk"; // LinearError includes: .status, .message, .type, .query, .variables // InvalidInputLinearError extends LinearError for mutation input errors
Error 1: Authentication Failures
// extensions.type: "authentication_error" // HTTP 401 or error in response.errors // Diagnostic check async function testAuth(): Promise<void> { try { const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! }); const viewer = await client.viewer; console.log(`OK: ${viewer.name} (${viewer.email})`); } catch (error: any) { if (error.message?.includes("Authentication")) { console.error("API key is invalid or expired."); console.error("Fix: Settings > Account > API > Personal API keys"); } throw error; } }
Quick curl diagnostic:
curl -s -X POST https://api.linear.app/graphql \ -H "Authorization: $LINEAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "{ viewer { id name email } }"}' | jq .
Error 2: Rate Limiting (HTTP 429)
Linear uses the leaky bucket algorithm with two budgets:
- Request limit: 5,000 requests/hour per API key
- Complexity limit: 250,000 complexity points/hour per API key
- Max single query complexity: 10,000 points
// extensions.type: "ratelimited" // HTTP 429 with rate limit headers async function withRetry<T>(fn: () => Promise<T>, maxRetries = 5): Promise<T> { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (error: any) { const isRateLimited = error.status === 429 || error.message?.includes("rate") || error.type === "ratelimited"; if (!isRateLimited || attempt === maxRetries - 1) throw error; const delay = 1000 * Math.pow(2, attempt) + Math.random() * 500; console.warn(`Rate limited (attempt ${attempt + 1}), waiting ${Math.round(delay)}ms`); await new Promise(r => setTimeout(r, delay)); } } throw new Error("Unreachable"); }
Check rate limit status via headers:
const resp = await fetch("https://api.linear.app/graphql", { method: "POST", headers: { Authorization: process.env.LINEAR_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ query: "{ viewer { id } }" }), }); console.log("Requests remaining:", resp.headers.get("x-ratelimit-requests-remaining")); console.log("Requests limit:", resp.headers.get("x-ratelimit-requests-limit")); console.log("Requests reset:", resp.headers.get("x-ratelimit-requests-reset")); console.log("Complexity:", resp.headers.get("x-complexity"));
Error 3: Query Complexity Too High
Each property = 0.1 pt, each object = 1 pt, connections multiply children by the
first argument (default 50). Max 10,000 pts per query.
// BAD: ~12,500 complexity (250 * 50 labels) const heavy = await client.issues({ first: 250 }); // GOOD: reduce page size and fetch relations separately const light = await client.issues({ first: 50 });
Error 4: Entity Not Found
// extensions.type: "not_found" // Cause: deleted, archived, wrong workspace, or insufficient permissions try { const issue = await client.issue("nonexistent-uuid"); } catch (error: any) { if (error.message?.includes("Entity not found")) { console.error("Issue may be deleted, archived, or in another workspace."); console.error("Try: client.issues({ includeArchived: true })"); } }
Error 5: Invalid Input on Mutations
import { InvalidInputLinearError } from "@linear/sdk"; try { await client.createIssue({ teamId: "invalid-uuid", title: "", // Empty title }); } catch (error) { if (error instanceof InvalidInputLinearError) { console.error("Invalid input:", error.message); // error.query and error.variables contain request details } }
Error 6: Null Reference on Relations
// SDK models lazy-load relations -- they can be null const issue = await client.issue("uuid"); // BAD: crashes if unassigned // const name = (await issue.assignee).name; // GOOD: optional chaining const name = (await issue.assignee)?.name ?? "Unassigned"; const projectName = (await issue.project)?.name ?? "No project";
Error 7: Webhook Signature Mismatch
// Happens when LINEAR_WEBHOOK_SECRET doesn't match the webhook config import crypto from "crypto"; function verifyWebhook(payload: string, signature: string, secret: string): boolean { const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex"); try { return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } catch { return false; // Length mismatch } }
Error Reference Table
| Error | extensions.type | HTTP | Cause | Fix |
|---|---|---|---|---|
| Authentication required | | 401 | Invalid/expired key | Regenerate at Settings > API |
| Forbidden | | 403 | Missing OAuth scope | Re-authorize with correct scopes |
| Rate limited | | 429 | Budget exceeded | Exponential backoff, reduce complexity |
| Query complexity too high | | 400 | Deep nesting or large pages | Reduce , flatten query |
| Entity not found | | 200 | Deleted/archived/wrong workspace | Verify ID, try |
| Validation error | | 200 | Bad mutation input | Check field constraints |
| Webhook sig mismatch | N/A (local) | N/A | Wrong signing secret | Match |
Examples
Catch-All Error Handler
import { LinearError, InvalidInputLinearError } from "@linear/sdk"; async function handleLinearOp<T>(fn: () => Promise<T>): Promise<T> { try { return await fn(); } catch (error) { if (error instanceof InvalidInputLinearError) { console.error(`Input error: ${error.message}`); } else if (error instanceof LinearError) { console.error(`Linear error [${error.status}]: ${error.message}`); if (error.status === 429) { console.error("Rate limited — implement backoff"); } } else { console.error("Unexpected error:", error); } throw error; } }