Claude-code-plugins-plus-skills linear-install-auth
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-install-auth" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-linear-install-auth && rm -rf "$T"
manifest:
plugins/saas-packs/linear-pack/skills/linear-install-auth/SKILL.mdsource content
Linear Install & Auth
Overview
Install the
@linear/sdk TypeScript SDK and configure authentication for the Linear GraphQL API at https://api.linear.app/graphql. Supports personal API keys for scripts and OAuth 2.0 (with PKCE) for user-facing apps.
Prerequisites
- Node.js 18+ (SDK is TypeScript-first, works in any JS environment)
- Package manager (npm, pnpm, or yarn)
- Linear account with workspace access
- For API key: Settings > Account > API > Personal API keys
- For OAuth: Create app at Settings > Account > API > OAuth applications
Instructions
Step 1: Install the SDK
set -euo pipefail npm install @linear/sdk # or: pnpm add @linear/sdk # or: yarn add @linear/sdk
The SDK exposes
LinearClient, typed models for every entity (Issue, Project, Cycle, Team), and error classes (LinearError, InvalidInputLinearError).
Step 2: API Key Authentication (Scripts & Server-Side)
Generate a Personal API key at Linear Settings > Account > API > Personal API keys. Keys start with
lin_api_ and are shown only once.
import { LinearClient } from "@linear/sdk"; // Environment variable (recommended) const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY, }); // Verify connection const me = await client.viewer; console.log(`Authenticated as: ${me.name} (${me.email})`);
Environment setup:
# .env (never commit) LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx # .gitignore echo '.env' >> .gitignore
Step 3: OAuth 2.0 Authentication (User-Facing Apps)
Linear supports the standard Authorization Code flow with optional PKCE. As of October 2025, newly created OAuth apps issue refresh tokens by default.
import crypto from "crypto"; // 1. Build authorization URL const SCOPES = ["read", "write", "issues:create"]; const state = crypto.randomBytes(16).toString("hex"); const authUrl = new URL("https://linear.app/oauth/authorize"); authUrl.searchParams.set("client_id", process.env.LINEAR_CLIENT_ID!); authUrl.searchParams.set("redirect_uri", process.env.LINEAR_REDIRECT_URI!); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("scope", SCOPES.join(",")); authUrl.searchParams.set("state", state); // Optional PKCE: // authUrl.searchParams.set("code_challenge", challenge); // authUrl.searchParams.set("code_challenge_method", "S256"); // 2. Exchange authorization code for tokens const tokenResponse = await fetch("https://api.linear.app/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code: authorizationCode, client_id: process.env.LINEAR_CLIENT_ID!, client_secret: process.env.LINEAR_CLIENT_SECRET!, redirect_uri: process.env.LINEAR_REDIRECT_URI!, }), }); const { access_token, refresh_token, expires_in } = await tokenResponse.json(); // 3. Create client with OAuth token const client = new LinearClient({ accessToken: access_token });
Available OAuth scopes:
read, write, issues:create, admin, initiative:read, initiative:write, customer:read, customer:write.
Step 4: Token Refresh (OAuth)
async function refreshAccessToken(refreshToken: string): Promise<string> { const response = await fetch("https://api.linear.app/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: process.env.LINEAR_CLIENT_ID!, client_secret: process.env.LINEAR_CLIENT_SECRET!, }), }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.status}`); } const tokens = await response.json(); // Store new refresh_token — Linear rotates it on each refresh await saveTokens(tokens.access_token, tokens.refresh_token); return tokens.access_token; }
Step 5: Validate Configuration on Startup
function validateLinearConfig(): void { const key = process.env.LINEAR_API_KEY; if (!key) throw new Error("LINEAR_API_KEY environment variable is required"); if (!key.startsWith("lin_api_")) throw new Error("LINEAR_API_KEY must start with lin_api_"); if (key.length < 30) throw new Error("LINEAR_API_KEY appears truncated"); } // Call before creating client validateLinearConfig();
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Invalid, expired, or missing API key | Regenerate at Settings > Account > API |
| Key doesn't start with | Copy full key from Linear (shown once) |
| Token lacks required OAuth scope | Re-authorize with correct scopes |
on token exchange | Code expired or PKCE verifier mismatch | Restart OAuth flow; codes expire quickly |
| SDK not installed | Run |
| DNS/firewall issue | Ensure outbound HTTPS to |
Examples
Raw GraphQL Without SDK
const response = await fetch("https://api.linear.app/graphql", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": process.env.LINEAR_API_KEY!, }, body: JSON.stringify({ query: `{ viewer { id name email } }`, }), }); const { data, errors } = await response.json(); if (errors) console.error("GraphQL errors:", errors); else console.log("Viewer:", data.viewer);
Server-to-Server (Client Credentials)
// For apps without user interaction — uses client_credentials grant const response = await fetch("https://api.linear.app/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.LINEAR_CLIENT_ID!, client_secret: process.env.LINEAR_CLIENT_SECRET!, scope: "read,write", }), }); const { access_token } = await response.json(); const client = new LinearClient({ accessToken: access_token });