Errors
All errors extend ClaudeError, which extends the native Error. You can catch them specifically or broadly.
ClaudeError
Base error class for all claude-wire errors.
import { ClaudeError } from "@pivanov/claude-wire";
try {
await claude.ask("...");
} catch (error) {
if (error instanceof ClaudeError) {
console.error("claude-wire error:", error.message);
}
}BudgetExceededError
Thrown when maxCostUsd is set and the cumulative cost exceeds the budget. The process is automatically killed.
import { BudgetExceededError } from "@pivanov/claude-wire";
try {
await claude.ask("...", { maxCostUsd: 0.10 });
} catch (error) {
if (error instanceof BudgetExceededError) {
console.error(`Spent $${error.spent.toFixed(4)} of $${error.budget.toFixed(4)} limit`);
}
}Properties:
spent: number- amount spent in USDbudget: number- the limit that was exceeded
AbortError
Thrown when the operation is cancelled via an AbortSignal.
import { AbortError } from "@pivanov/claude-wire";
try {
await claude.ask("...", { signal: AbortSignal.timeout(5000) });
} catch (error) {
if (error instanceof AbortError) {
console.error("Request was cancelled");
}
}TimeoutError
Thrown when an operation times out. Distinct from AbortError for cases where the SDK itself enforces a timeout.
ProcessError
Thrown when the Claude Code process exits with a non-zero exit code or fails to spawn.
import { ProcessError } from "@pivanov/claude-wire";
try {
await claude.ask("...");
} catch (error) {
if (error instanceof ProcessError) {
console.error(`Process exited with code ${error.exitCode}`);
}
}Properties:
exitCode?: number- the process exit code, if available
KnownError
For expected, user-facing errors with a machine-readable code. Extends ClaudeError.
import { KnownError, isKnownError } from "@pivanov/claude-wire";
try {
await claude.ask("...");
} catch (error) {
if (isKnownError(error)) {
console.error(`Known error [${error.code}]: ${error.message}`);
}
}Properties:
code: TKnownErrorCode- one of:"not-authenticated","binary-not-found","permission-denied","retry-exhausted","rate-limit","overloaded","context-length-exceeded","invalid-json-schema","mcp-error"
The last five codes are auto-classified from stderr by classifyStderr() -- when a ProcessError is about to be thrown and stderr matches a known pattern, it is promoted to a KnownError with the appropriate code instead.
The retry-exhausted code is thrown by session.ask() when the respawn budget (LIMITS.maxRespawnAttempts, currently 3) has been used up by consecutive transient failures. The session is marked closed and any further ask() call rejects with ClaudeError("Session is closed").
import { isKnownError } from "@pivanov/claude-wire";
try {
await session.ask("...");
} catch (error) {
if (isKnownError(error) && error.code === "retry-exhausted") {
// Session is dead -- create a new one before calling ask() again.
}
}classifyStderr(stderr, exitCode?)
Attempts to classify an opaque stderr string into a typed TKnownErrorCode. Returns undefined when no pattern matches.
import { classifyStderr } from "@pivanov/claude-wire";
const code = classifyStderr("Error: rate limit exceeded (429)");
console.log(code); // "rate-limit"
const unknown = classifyStderr("something unexpected");
console.log(unknown); // undefinedRecognized patterns:
| Code | Matches |
|---|---|
rate-limit | rate limit, 429, too many requests |
overloaded | overloaded, 529, temporarily unavailable |
context-length-exceeded | context length, context window, too long, maximum.*tokens |
invalid-json-schema | invalid.*json schema, schema.*invalid, json.*schema.*error |
mcp-error | mcp.*error, mcp.*fail, mcp.*server |
not-authenticated | not authenticated, authentication, unauthorized, 401 |
permission-denied | permission denied, forbidden, 403 |
binary-not-found | binary.*not found, command not found, ENOENT.*claude |
This function is wired into the error factory at module load -- ProcessError instances are automatically promoted to KnownError when stderr matches. You typically don't need to call it directly unless you're doing custom stderr analysis.
JsonValidationError
Thrown by askJson() when the response cannot be parsed as valid JSON or fails schema validation.
import { JsonValidationError } from "@pivanov/claude-wire";
try {
await claude.askJson("...", schema);
} catch (error) {
if (error instanceof JsonValidationError) {
console.error("Raw text:", error.rawText);
console.error("Issues:", error.issues);
}
}Properties:
rawText: string-- the raw text that failed to parse or validateissues: ReadonlyArray<{ message?: string; path?: ReadonlyArray<string | number> }>-- validation issues from the schema library
isTransientError(error)
Detects transient errors that may succeed on retry (network issues, signal kills). Returns false for AbortError and BudgetExceededError (those are intentional, not transient). createSession() uses this classifier internally to decide which failures trigger auto-respawn.
import { isTransientError } from "@pivanov/claude-wire";
if (isTransientError(error)) {
// safe to retry
}Matches:
- Network / DNS:
ECONNREFUSED,ECONNRESET,ECONNABORTED,ETIMEDOUT,ENETUNREACH,EHOSTUNREACH,EAI_AGAIN,network error,network timeout,fetch failed,socket hang up - Pipe / signal:
EPIPE,SIGPIPE,broken pipe - Anthropic overload:
overloaded_error(the CLI bubbles up 529 responses verbatim) - Process exit codes:
137(SIGKILL / OOM),141(SIGPIPE),143(SIGTERM)