Back to list
jeremylongshore

linear-debug-bundle

by jeremylongshore

Hundreds of Claude Code plugins with embedded AI skills. Learn via interactive Jupyter tutorials.

1,042🍴 135📅 Jan 23, 2026

SKILL.md


name: linear-debug-bundle description: | Comprehensive debugging toolkit for Linear integrations. Use when setting up logging, tracing API calls, or building debug utilities for Linear. Trigger with phrases like "debug linear integration", "linear logging", "trace linear API", "linear debugging tools", "linear troubleshooting". allowed-tools: Read, Write, Edit, Grep, Bash(node:), Bash(npx:) version: 1.0.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io

Linear Debug Bundle

Overview

Comprehensive debugging tools for Linear API integrations.

Prerequisites

  • Linear SDK configured
  • Node.js environment
  • Optional: logging library (pino, winston)

Instructions

Step 1: Create Debug Client Wrapper

// lib/debug-client.ts
import { LinearClient } from "@linear/sdk";

interface DebugOptions {
  logRequests?: boolean;
  logResponses?: boolean;
  logErrors?: boolean;
  onRequest?: (query: string, variables: unknown) => void;
  onResponse?: (data: unknown, duration: number) => void;
  onError?: (error: Error, duration: number) => void;
}

export function createDebugClient(
  apiKey: string,
  options: DebugOptions = {}
): LinearClient {
  const {
    logRequests = true,
    logResponses = true,
    logErrors = true,
  } = options;

  // Create client with custom fetch for logging
  const client = new LinearClient({
    apiKey,
    fetch: async (url, init) => {
      const start = Date.now();
      const body = init?.body ? JSON.parse(init.body as string) : null;

      if (logRequests && body) {
        console.log("[Linear Request]", {
          query: body.query?.slice(0, 100) + "...",
          variables: body.variables,
        });
        options.onRequest?.(body.query, body.variables);
      }

      try {
        const response = await fetch(url, init);
        const duration = Date.now() - start;
        const data = await response.clone().json();

        if (logResponses) {
          console.log("[Linear Response]", {
            duration: `${duration}ms`,
            hasErrors: !!data.errors,
            dataKeys: data.data ? Object.keys(data.data) : [],
          });
          options.onResponse?.(data, duration);
        }

        return response;
      } catch (error) {
        const duration = Date.now() - start;
        if (logErrors) {
          console.error("[Linear Error]", {
            duration: `${duration}ms`,
            error: error instanceof Error ? error.message : error,
          });
          options.onError?.(error as Error, duration);
        }
        throw error;
      }
    },
  });

  return client;
}

Step 2: Request Tracer

// lib/tracer.ts
interface TraceEntry {
  id: string;
  operation: string;
  startTime: Date;
  endTime?: Date;
  duration?: number;
  success: boolean;
  error?: string;
  metadata?: Record<string, unknown>;
}

class LinearTracer {
  private traces: TraceEntry[] = [];
  private maxTraces = 100;

  startTrace(operation: string, metadata?: Record<string, unknown>): string {
    const id = crypto.randomUUID();
    this.traces.push({
      id,
      operation,
      startTime: new Date(),
      success: false,
      metadata,
    });

    // Trim old traces
    if (this.traces.length > this.maxTraces) {
      this.traces = this.traces.slice(-this.maxTraces);
    }

    return id;
  }

  endTrace(id: string, success: boolean, error?: string): void {
    const trace = this.traces.find(t => t.id === id);
    if (trace) {
      trace.endTime = new Date();
      trace.duration = trace.endTime.getTime() - trace.startTime.getTime();
      trace.success = success;
      trace.error = error;
    }
  }

  getTraces(): TraceEntry[] {
    return [...this.traces];
  }

  getSlowTraces(thresholdMs = 1000): TraceEntry[] {
    return this.traces.filter(t => (t.duration ?? 0) > thresholdMs);
  }

  getFailedTraces(): TraceEntry[] {
    return this.traces.filter(t => !t.success);
  }

  getSummary(): Record<string, unknown> {
    const completed = this.traces.filter(t => t.duration !== undefined);
    const durations = completed.map(t => t.duration!);

    return {
      total: this.traces.length,
      completed: completed.length,
      failed: this.getFailedTraces().length,
      avgDuration: durations.length
        ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length)
        : 0,
      maxDuration: Math.max(...durations, 0),
    };
  }
}

export const tracer = new LinearTracer();

Step 3: Health Check Utility

// lib/health.ts
import { LinearClient } from "@linear/sdk";

interface HealthCheckResult {
  healthy: boolean;
  latencyMs: number;
  user?: { name: string; email: string };
  teams?: number;
  error?: string;
  timestamp: Date;
}

export async function checkLinearHealth(
  client: LinearClient
): Promise<HealthCheckResult> {
  const start = Date.now();

  try {
    const [viewer, teams] = await Promise.all([
      client.viewer,
      client.teams(),
    ]);

    return {
      healthy: true,
      latencyMs: Date.now() - start,
      user: { name: viewer.name, email: viewer.email },
      teams: teams.nodes.length,
      timestamp: new Date(),
    };
  } catch (error) {
    return {
      healthy: false,
      latencyMs: Date.now() - start,
      error: error instanceof Error ? error.message : "Unknown error",
      timestamp: new Date(),
    };
  }
}

// Express/Koa endpoint
export function healthEndpoint(client: LinearClient) {
  return async (req: any, res: any) => {
    const result = await checkLinearHealth(client);
    res.status(result.healthy ? 200 : 503).json(result);
  };
}

Step 4: Debug Console Commands

// debug/cli.ts
import { LinearClient } from "@linear/sdk";
import readline from "readline";

const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY! });

const commands: Record<string, () => Promise<void>> = {
  async me() {
    const viewer = await client.viewer;
    console.log("Current user:", viewer.name, viewer.email);
  },

  async teams() {
    const teams = await client.teams();
    console.log("Teams:");
    teams.nodes.forEach(t => console.log(`  ${t.key}: ${t.name}`));
  },

  async issues() {
    const issues = await client.issues({ first: 10 });
    console.log("Recent issues:");
    issues.nodes.forEach(i => console.log(`  ${i.identifier}: ${i.title}`));
  },

  async states() {
    const teams = await client.teams();
    for (const team of teams.nodes) {
      const states = await team.states();
      console.log(`\n${team.key} workflow:`);
      states.nodes.forEach(s => console.log(`  ${s.name} (${s.type})`));
    }
  },

  help() {
    console.log("Commands: me, teams, issues, states, help, exit");
    return Promise.resolve();
  },
};

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: "linear> ",
});

rl.prompt();
rl.on("line", async (line) => {
  const cmd = line.trim().toLowerCase();
  if (cmd === "exit") {
    rl.close();
    return;
  }
  if (commands[cmd]) {
    await commands[cmd]();
  } else {
    console.log("Unknown command. Type 'help' for available commands.");
  }
  rl.prompt();
});

Step 5: Environment Validator

// lib/validate-env.ts
interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
}

export function validateLinearEnv(): ValidationResult {
  const errors: string[] = [];
  const warnings: string[] = [];

  // Required
  if (!process.env.LINEAR_API_KEY) {
    errors.push("LINEAR_API_KEY is not set");
  } else if (!process.env.LINEAR_API_KEY.startsWith("lin_api_")) {
    errors.push("LINEAR_API_KEY has invalid format (should start with lin_api_)");
  }

  // Optional but recommended
  if (!process.env.LINEAR_WEBHOOK_SECRET) {
    warnings.push("LINEAR_WEBHOOK_SECRET not set (webhooks won't be verified)");
  }

  if (process.env.NODE_ENV === "production" && !process.env.LINEAR_API_KEY?.includes("prod")) {
    warnings.push("Using non-production API key in production environment");
  }

  return {
    valid: errors.length === 0,
    errors,
    warnings,
  };
}

// Run validation on import
const result = validateLinearEnv();
if (!result.valid) {
  console.error("Linear environment validation failed:", result.errors);
}
result.warnings.forEach(w => console.warn("Linear warning:", w));

Output

  • Debug client with request/response logging
  • Request tracer with performance metrics
  • Health check endpoint
  • Interactive debug console
  • Environment validator

Error Handling

ErrorCauseSolution
Circular JSONLogging full Linear objectsUse selective logging
Memory leakUnbounded trace storageSet maxTraces limit
Missing envValidation failedCheck environment setup

Resources

Next Steps

Learn rate limiting strategies with linear-rate-limits.

Score

Total Score

85/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 1000以上

+15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

+5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon