linear-common-errors by jeremylongshore
Diagnose and fix common Linear API and SDK errors.
Content & Writing
2.5K Stars
355 Forks
Updated May 23, 2026, 05:41 AM
Why Use This
This skill provides specialized capabilities for jeremylongshore's codebase.
Use Cases
- Developing new features in the jeremylongshore repository
- Refactoring existing code to follow jeremylongshore standards
- Understanding and working with jeremylongshore's codebase structure
Install Guide
2 steps- 1
Skip this step if Ananke is already installed.
- 2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Repository claude-code-plugins-plus-skills
Skill Version
main
Community
2.5K 355
Updated At May 23, 2026, 05:41 AM
Skill Stats
SKILL.md 261 Lines
Total Files 2
Total Size 7.9 KB
License MIT
---
name: linear-common-errors
description: 'Diagnose and fix common Linear API and SDK errors.
Use when encountering Linear API errors, debugging integration issues,
or troubleshooting authentication, rate limits, or query problems.
Trigger: "linear error", "linear API error", "debug linear",
"linear not working", "linear 429", "linear authentication error".
'
allowed-tools: Read, Write, Edit, Grep, Bash(curl:*)
version: 1.0.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- linear
- api
- debugging
- authentication
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
---
# 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
```typescript
// 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
```typescript
// 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:**
```bash
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
```typescript
// 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:**
```typescript
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.
```typescript
// 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
```typescript
// 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
```typescript
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
```typescript
// 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
```typescript
// 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_error` | 401 | Invalid/expired key | Regenerate at Settings > API |
| Forbidden | `forbidden` | 403 | Missing OAuth scope | Re-authorize with correct scopes |
| Rate limited | `ratelimited` | 429 | Budget exceeded | Exponential backoff, reduce complexity |
| Query complexity too high | `query_error` | 400 | Deep nesting or large pages | Reduce `first`, flatten query |
| Entity not found | `not_found` | 200 | Deleted/archived/wrong workspace | Verify ID, try `includeArchived` |
| Validation error | `invalid_input` | 200 | 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
```typescript
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
- [SDK Error Handling](https://linear.app/developers/sdk-errors)
- [Rate Limiting](https://linear.app/developers/rate-limiting)
- [GraphQL API](https://linear.app/developers/graphql)
Name Size