Skip to content

Reference

Errors

Every Pluck error extends PluckError. Each carries a code (stable string), a phase (connect / navigate / extract / shape / act / sense / output), and where applicable a cause chain and a structured details payload.


The hierarchy

PluckError
├── ConnectionError       – connect phase, network-level failures
├── AuthenticationError   – connect phase, bad credentials
├── RateLimitError        – connect phase, 429s
├── TimeoutError          – any phase, per-call timeout exceeded
├── NavigationError       – navigate phase
├── ExtractionError       – extract phase
├── TranscriptionError    – extract phase, Whisper-specific
├── ActionError           – act phase
├── SenseError            – sense phase
├── OutputError           – output phase, format rendering failures
└── InvalidInputError     – argument validation anywhere

Every subclass sets phase and a stable code. Catch the base PluckError to handle anything; catch a subclass when you want to react to a specific failure.

TypeScript
import { pluck, PluckError, ConnectionError, RateLimitError } from "@sizls/pluck";

try {
  await pluck("https://flaky.example.com");
} catch (err) {
  if (err instanceof RateLimitError) {
    await sleep(err.retryAfterMs ?? 30_000);
    return retry();
  }
  if (err instanceof ConnectionError) {
    console.error("Network:", err.code, err.message);
    return;
  }
  if (err instanceof PluckError) {
    console.error(`[${err.phase}] ${err.code}: ${err.message}`);
    return;
  }
  throw err; // unknown – let it bubble.
}

Stable error codes

code strings are load-bearing – CI, monitors, dashboards, and alerts branch on them. Every code below is stable and will not change in a minor version:

Connect

CodeWhen it fires
CONNECTION_ERRORGeneric connect failure.
CONNECTION_REFUSEDTCP refused.
DNS_ERRORHostname didn't resolve.
SSRF_BLOCKEDURL resolved to a private / link-local / reserved range.
BODY_TOO_LARGEBody exceeded MAX_BODY_BYTES.
LINE_TOO_LARGESingle stream line exceeded MAX_LINE_BYTES.
SESSION_TOO_LARGESession total exceeded MAX_SESSION_BYTES.
MISSING_PEER_DEPOptional peer not installed (e.g. ssh2, kafkajs).
NO_CONNECTORNo connector claimed the URI.
AUTH_ERRORAuthentication failed (401 / 403).
RATE_LIMITEDSource returned 429. retryAfterMs on the error.
CodeWhen it fires
NAVIGATION_ERRORGeneric navigate-phase failure.
NO_NAVIGATORMode requested but no navigator registered (or falls through to direct).
EXTRACTION_ERRORGeneric extract-phase failure.
NO_EXTRACTORNo extractor claimed the navigate result.
TRANSCRIPTION_ERRORWhisper provider failed or returned empty.
SHAPE_VALIDATION_FAILEDshape returned valid: false.

Act

CodeWhen it fires
NO_ACTORNo actor claimed the URI for the requested action.
ACTION_NOT_SUPPORTEDActor matched but doesn't implement the requested action name.
ACTION_NOT_REVERSIBLEundo() called on an action whose actor didn't declare an inverse.
RECEIPT_VERIFICATION_FAILEDverifyReceipt / undo – signature invalid or key mismatch.
POLICY_DENIED.pluckpolicy.yaml deny rule matched the action preview.
CONFIRM_REJECTEDConfirmation callback returned false.
UNDO_FAILEDInverse action threw.
MISSING_SIGNING_KEYreceipts.sign() called without a key.

Sense / Output / Generic

CodeWhen it fires
NO_SENSORRequested feature isn't shipping (e.g. flicker is a PlannedSenseFeature).
UNSUPPORTED_FORMATAudio source not WAV (use ffmpeg to convert).
SENSE_ERRORGeneric sense-phase failure.
NO_FORMATTERresult.output("foo") with no registered foo formatter.
OUTPUT_ERRORFormatter threw.
TIMEOUTPer-call timeout exceeded. Carries phase.
INVALID_INPUTArgument validation failed. Typically 400-equivalent.
INSTANCE_DESTROYEDA call was made on a PluckInstance after .destroy().

Error shape

Every Pluck error carries the same shape:

TypeScript
class PluckError extends Error {
  readonly code: string;               // Stable code above
  readonly phase: PluckPhase;          // "connect" | ... | "output"
  readonly cause?: unknown;            // Original error, when wrapping
  readonly details?: Record<string, unknown>; // Phase-specific metadata
}

details carries phase-specific context – retryAfterMs on RateLimitError, blockedHost on SSRF_BLOCKED, stripped fields on SHAPE_VALIDATION_FAILED. Every field is documented on the subclass JSDoc.


Serialisation (for logs + traces)

Pluck errors serialise cleanly to JSON:

TypeScript
import { pluck } from "@sizls/pluck";

const safe = await pluck.safe("https://broken.example.com");

if (!safe.success) {
  logger.error(JSON.stringify(safe.error, null, 2));
  // {
  //   "name": "ConnectionError",
  //   "code": "DNS_ERROR",
  //   "phase": "connect",
  //   "message": "getaddrinfo ENOTFOUND broken.example.com",
  //   "details": { "hostname": "broken.example.com" }
  // }
}

The PluckError.toJSON() override guarantees the same shape whether you console.log it, ship it to Sentry, or store it in a Studio trace.


pluck.safe() – never-throws variant

Prefer discriminated unions over try/catch? Every pluck call has a .safe() shadow:

TypeScript
const res = await pluck.safe("https://example.com");

if (res.success) {
  console.log(res.data.text);
} else {
  console.error(`[${res.error.phase}] ${res.error.code}`);
}

SafeResult<T> is { success: true; data: T } | { success: false; error: PluckError }. Use it inside hot loops where you don't want control-flow via exceptions.


What's next

Edit this page on GitHub
Previous
API

Ready to build?

Install Pluck and follow the Quick Start guide to wire MCP-first data pipelines into your agents and fleets in minutes.

Get started →