Skip to main content Diagnose and fix common Linear API errors.
Use when encountering Linear API errors, debugging integration issues,
or troubleshooting authentication problems.
Trigger with phrases like "linear error", "linear API error",
"debug linear", "linear not working", "linear authentication error".
npx skills add jeremylongshore/claude-code-plugins-plus-skills --skill linear-common-errors ai automation claude-code devops mcp ai-agents
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;
}
}
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 authentication_error401 Invalid/expired key Regenerate at Settings > API Forbidden forbidden403 Missing OAuth scope Re-authorize with correct scopes Rate limited ratelimited429 Budget exceeded Exponential backoff, reduce complexity Query complexity too high query_error400 Deep nesting or large pages Reduce first, flatten query Entity not found not_found200 Deleted/archived/wrong workspace Verify ID, try includeArchived Validation error invalid_input200 Bad mutation input Check field constraints Webhook sig mismatch N/A (local) N/A Wrong signing secret Match LINEAR_WEBHOOK_SECRET
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;
}
}
Resources Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).