- Docs
- Reference
- Errors
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.
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
| Code | When it fires |
|---|---|
CONNECTION_ERROR | Generic connect failure. |
CONNECTION_REFUSED | TCP refused. |
DNS_ERROR | Hostname didn't resolve. |
SSRF_BLOCKED | URL resolved to a private / link-local / reserved range. |
BODY_TOO_LARGE | Body exceeded MAX_BODY_BYTES. |
LINE_TOO_LARGE | Single stream line exceeded MAX_LINE_BYTES. |
SESSION_TOO_LARGE | Session total exceeded MAX_SESSION_BYTES. |
MISSING_PEER_DEP | Optional peer not installed (e.g. ssh2, kafkajs). |
NO_CONNECTOR | No connector claimed the URI. |
AUTH_ERROR | Authentication failed (401 / 403). |
RATE_LIMITED | Source returned 429. retryAfterMs on the error. |
Navigate / Extract / Shape
| Code | When it fires |
|---|---|
NAVIGATION_ERROR | Generic navigate-phase failure. |
NO_NAVIGATOR | Mode requested but no navigator registered (or falls through to direct). |
EXTRACTION_ERROR | Generic extract-phase failure. |
NO_EXTRACTOR | No extractor claimed the navigate result. |
TRANSCRIPTION_ERROR | Whisper provider failed or returned empty. |
SHAPE_VALIDATION_FAILED | shape returned valid: false. |
Act
| Code | When it fires |
|---|---|
NO_ACTOR | No actor claimed the URI for the requested action. |
ACTION_NOT_SUPPORTED | Actor matched but doesn't implement the requested action name. |
ACTION_NOT_REVERSIBLE | undo() called on an action whose actor didn't declare an inverse. |
RECEIPT_VERIFICATION_FAILED | verifyReceipt / undo – signature invalid or key mismatch. |
POLICY_DENIED | .pluckpolicy.yaml deny rule matched the action preview. |
CONFIRM_REJECTED | Confirmation callback returned false. |
UNDO_FAILED | Inverse action threw. |
MISSING_SIGNING_KEY | receipts.sign() called without a key. |
Sense / Output / Generic
| Code | When it fires |
|---|---|
NO_SENSOR | Requested feature isn't shipping (e.g. flicker is a PlannedSenseFeature). |
UNSUPPORTED_FORMAT | Audio source not WAV (use ffmpeg to convert). |
SENSE_ERROR | Generic sense-phase failure. |
NO_FORMATTER | result.output("foo") with no registered foo formatter. |
OUTPUT_ERROR | Formatter threw. |
TIMEOUT | Per-call timeout exceeded. Carries phase. |
INVALID_INPUT | Argument validation failed. Typically 400-equivalent. |
INSTANCE_DESTROYED | A call was made on a PluckInstance after .destroy(). |
Error shape
Every Pluck error carries the same shape:
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:
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:
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
- Concepts: Connect – most connect-phase errors.
- Concepts: Act –
POLICY_DENIED,ACTION_NOT_REVERSIBLE,RECEIPT_VERIFICATION_FAILED. - Reference: CLI – exit-code mapping.