SmithersErrorInstance is a typed, code-bearing Error subclass used throughout Smithers internals. It surfaces when runWorkflow throws, in NodeFailed events emitted during execution, and as JSON in HTTP API error responses. The imports below are the full error utility surface.
build agent command: undefined is not an object (evaluating 'schema._zod.def'):
your task output schema is a Zod v3 object; Smithers reads schema metadata
via Zod v4 internals (_zod.def). Install Zod v4 (bun add zod@^4) and import
z from it. Deterministic configuration error: re-running won’t fix it.import {
ERROR_REFERENCE_URL,
SmithersErrorInstance,
errorToJson,
getSmithersErrorDefinition,
getSmithersErrorDocsUrl,
isKnownSmithersErrorCode,
isSmithersError,
knownSmithersErrorCodes,
} from "smthrs";
import type {
KnownSmithersErrorCode,
SmithersError,
SmithersErrorCode,
} from "smthrs";
SmithersErrorInstance carries three pieces of documentation metadata:
| Field | Meaning |
|---|---|
message | Human-readable description followed by a docs URL, e.g. "Input failed validation. See https://…" |
summary | Raw message without the docs suffix. |
docsUrl | Reference URL for Smithers errors. |
KnownSmithersErrorCode for an exhaustive switch over built-in Smithers codes. SmithersErrorCode includes the (string & {}) escape hatch for user-defined custom codes.
| Export | Kind | Description |
|---|---|---|
SmithersErrorInstance | class | Runtime error class used throughout Smithers internals. |
isSmithersError(err) | function | Type guard for values carrying a Smithers-style code. |
isKnownSmithersErrorCode(code) | function | Narrows a string to the built-in exhaustive error-code union. |
knownSmithersErrorCodes | value | Array of every built-in Smithers error code on this page. |
getSmithersErrorDocsUrl(code) | function | Returns the docs URL appended to built-in error messages. |
getSmithersErrorDefinition(code) | function | Returns category, description, and details metadata for known codes. |
errorToJson(err) | function | Serializes name, message, summary, docsUrl, code, details, cause, and stack. |
ERROR_REFERENCE_URL | value | Base docs URL for Smithers runtime errors. |
KnownSmithersErrorCode | type | Exact built-in Smithers code union. |
SmithersErrorCode | type | Built-in codes plus the custom string escape hatch. |
SmithersError | type | Public typed shape for serialized Smithers errors. |
import { Effect } from "effect";
import { runWorkflow } from "smthrs";
try {
await Effect.runPromise(runWorkflow(workflow, { input: {} }));
} catch (err) {
if (isSmithersError(err) && isKnownSmithersErrorCode(err.code)) {
switch (err.code) {
case "INVALID_INPUT":
console.error("Bad input:", err.summary);
break;
case "AGENT_CLI_ERROR":
console.error("Agent failed:", err.summary);
break;
default:
console.error(`[${err.code}] ${err.summary}`);
}
console.error("Docs:", err.docsUrl);
}
}
Engine
| Code | When | Details |
|---|---|---|
INVALID_INPUT | Workflow input fails validation or the runtime receives a non-object input payload. | — |
MISSING_INPUT | A resume run references an input row missing from the database. | — |
MISSING_INPUT_TABLE | Workflow schema doesn’t expose the expected input table during resume or hydration. | — |
RESUME_METADATA_MISMATCH | Stored run metadata no longer matches the workflow being resumed. Editing the workflow file or an imported module between stop and resume triggers this (resume hashes file content, not git; no commit required). Fork/replay onto the edit or start fresh; revert the file to resume the original run. | mismatches, existing, current |
UNKNOWN_OUTPUT_SCHEMA | A task references an output table not present in the schema registry. | — |
INVALID_OUTPUT | Agent output cannot be parsed or validated against the declared output schema. | — |
INVALID_RETRY_STATE | A resumed task has malformed or inconsistent durable retry metadata, so Smithers cannot safely reconstruct its retry state. | { nodeId, iteration, attempt } |
AGENT_CHECKPOINT_INVALID | An agent returned or published a malformed, non-JSON, or oversized checkpoint. | { nodeId, attempt } |
AGENT_CHECKPOINT_CAPABILITY_UNDECLARED | An agent returned or published a checkpoint codec/version absent from its checkpointFormats production declaration. | { nodeId, codec, version, checkpointFormats } |
AGENT_CHECKPOINT_HISTORY_EXHAUSTED | More than 1,000 newer incompatible checkpoint references prevent a safe same-task resume. | { nodeId, iteration, scanned } |
AGENT_CHECKPOINT_MISSING | A durable checkpoint reference points to missing content. | { contentHash } |
AGENT_CHECKPOINT_CORRUPT | Stored checkpoint bytes are invalid or disagree with reference metadata. | { contentHash } |
WORKTREE_CREATE_FAILED | Smithers fails to create or hydrate a git or jj worktree for a task. | { worktreePath, vcsType, branch? } |
VCS_NOT_FOUND | No supported git or jj repository root is found for the workflow. | { rootDir } |
SNAPSHOT_NOT_FOUND | A requested time-travel snapshot or frame does not exist. | { runId, frameNo } |
TIME_TRAVEL_SIDE_EFFECT_BLOCKED | A time-travel operation would cross an external side effect that was not reverted or explicitly forced. | { runId, operation, report } |
VCS_WORKSPACE_CREATE_FAILED | Smithers fails to materialize a jj workspace for time-travel or replay. | { runId, frameNo, vcsPointer, workspacePath } |
TASK_EMPTY_PROMPT | A <Task> prompt renders to an empty string, invoking the agent with no input. | { nodeId, iteration } |
WORKFLOW_RENDER_FAILED | The workflow component throws while rendering the graph (e.g. a hook called outside a component render, or a bug in the workflow function). | { workflowPath } |
TASK_TIMEOUT | A task compute callback exceeds its configured timeout. | { nodeId, attempt, timeoutMs } |
TASK_HIJACK_UNSUPPORTED | A task requests auto-hijack but its agent cannot provide a resumable session or conversation. | { nodeId, agentId? } |
TASK_FORK_SOURCE_NOT_COMPLETE | A forked task began executing but its fork source hasn’t completed, so no session snapshot exists yet. | { nodeId, forkSource } |
TASK_FORK_SESSION_UNAVAILABLE | A <Task fork> cannot obtain usable agent state: either the forking task isn’t an agent task, or the source produced neither a compatible checkpoint nor a forkable conversation (e.g. a compute/static, skipped, or cancelled source). | { nodeId, forkSource } |
TASK_FORK_CHECKPOINT_INCOMPATIBLE | A <Task fork> source has only a native checkpoint whose codec, version, or fork mode the target cannot consume. | { nodeId, forkSource, codec, version, mode } |
TASK_ABORTED | A running task is aborted through an AbortSignal or shutdown path. | — |
RUN_NOT_FOUND | A CLI or engine command references a run ID missing from the database. | { runId } |
NODE_NOT_FOUND | A CLI command references a node ID missing for the given run. | { runId, nodeId } |
SANDBOX_BUNDLE_INVALID | A sandbox bundle fails validation (missing README, invalid manifest, etc.). | { bundlePath } |
SANDBOX_BUNDLE_TOO_LARGE | A sandbox bundle exceeds the maximum allowed size. | { bundlePath, maxBytes } |
WORKFLOW_EXECUTION_FAILED | A child or builder workflow exits unsuccessfully without surfacing a typed error payload. | { status } |
WORKFLOW_TOOL_CHILD_FAILED | A workflow invoked by an agent tool ends unsuccessfully. | { toolName, childRunId, status, childError? } |
WORKFLOW_TOOL_SUSPENDED | A workflow invoked by an agent tool parks for approval, human input, an event, a timer, quota, or an explicit pause. | { toolName, childRunId, status } |
WORKFLOW_TOOL_DEPTH_EXCEEDED | Nested workflow-tool calls exceed the configured recursion depth. | { toolName, maxDepth, parentRunId } |
WORKFLOW_TOOL_TIMEOUT | A workflow invoked by an agent tool exceeds its wall-time limit. | { toolName, childRunId, timeoutMs } |
SANDBOX_EXECUTION_FAILED | Sandbox setup or execution fails before a more specific sandbox error can be emitted. | { sandboxId, runId?, maxConcurrent?, activeSandboxCount? } |
TASK_HEARTBEAT_TIMEOUT | A task heartbeat timeout expires while the task is still running. | { nodeId, iteration, attempt, timeoutMs, staleForMs, lastHeartbeatAtMs } |
AGENT_WORKER_EXITED | A spawned agent worker process exits without the attempt reaching a terminal state. The attempt fails and feeds the normal retry / fallbackAgents chain. | { nodeId, iteration, attempt, pid, exitCode, signal, attemptRunningForMs, sinceWorkerExitMs, graceMs } |
HEARTBEAT_PAYLOAD_TOO_LARGE | A task heartbeat payload exceeds the maximum persisted checkpoint size. | { dataSizeBytes, maxBytes } |
HEARTBEAT_PAYLOAD_NOT_JSON_SERIALIZABLE | A task heartbeat payload contains values not serializable to JSON. | { path, valueType? } |
RUN_CANCELLED | A run is cancelled while runtime work is still active. | { runId } |
RUN_NOT_RESUMABLE | A resume request targets a run state that cannot be resumed. | { runId, status } |
RUN_OWNER_ALIVE | A resume, retry, or time-travel attempt is refused because the run still has a live driver: its owner PID is alive on this host, a remote owner is still heartbeating, or a durable resume claim is still held. Normal behavior that stops two engines from driving one run. --force does not defeat it; pass --steal-ownership to take the run anyway. | { runId, runtimeOwnerId, ownerPid, evidence } |
RUN_STILL_RUNNING | A recovery or resume operation finds a run still active by heartbeat freshness alone. Relaxed by --force (or --steal-ownership). | { runId } |
RUN_RESUME_CLAIM_LOST | A runtime loses the resume claim before it can update the run. | { runId, runtimeOwnerId } |
RUN_RESUME_CLAIM_FAILED | A runtime cannot claim a stale run for resume. | { runId, runtimeOwnerId } |
RUN_RESUME_ACTIVATION_FAILED | A claimed run cannot be moved back into active execution. | { runId, runtimeOwnerId } |
AUTO_RESUME_GAVE_UP | The supervisor stops auto-resuming a run after consecutive detached resumes died before the engine activated, and marks the run failed with the resume log location. The run stays manually resumable once the startup failure is fixed. | { attempts, lastClaimOwnerId, logFile, logTail? } |
RUN_HIJACKED | A run is interrupted because another runtime hijacked execution. | { runId, hijackTarget } |
CONTINUATION_STATE_TOO_LARGE | Continue-as-new state exceeds the configured serialized size limit. | { runId, sizeBytes, maxBytes } |
INVALID_CONTINUATION_STATE | Continue-as-new state cannot be parsed or applied. | — |
RALPH_MAX_REACHED | A Ralph loop reaches maxIterations with fail-on-max behavior. | { ralphId, maxIterations } |
SCHEDULER_ERROR | The scheduler cannot produce a valid execution decision. | — |
SESSION_ERROR | The workflow session state machine reaches an invalid or failed state. | — |
TASK_STALLED | A task stopped retrying after consecutive attempts failed with an identical error signature (non-progress detection) and the run failed. | { key, nodeId, attempts, identicalFailures, signature } |
TASK_REPAIR_FAILED | A declared task repair exhausts its finite attempt budget or fails terminally. | { nodeId, repairNodeId, attempts } |
Components
| Code | When | Details |
|---|---|---|
TASK_ID_REQUIRED | <Task> is missing a valid string id. | — |
TASK_MISSING_OUTPUT | <Task> is missing its output prop. | { nodeId } |
TASK_FORK_SOURCE_NOT_FOUND | A <Task fork> references a source task id not present in the workflow graph, including one that exists only in an unselected branch. | { nodeId, forkSource } |
TASK_FORK_CYCLE | A <Task fork> introduces a dependency cycle, directly or indirectly. | { nodeId, forkSource } |
DUPLICATE_ID | Two nodes with the same runtime id are mounted in one workflow graph. | { kind, id } |
NESTED_LOOP | <Loop> or <Ralph> is nested inside another loop construct Smithers doesn’t support. | — |
WORKTREE_EMPTY_PATH | <Worktree> is mounted with an empty path. | — |
MDX_PRELOAD_INACTIVE | A prompt object is rendered without the MDX preload layer being active. | — |
CONTEXT_OUTSIDE_WORKFLOW | Workflow context access happens outside an active Smithers workflow render. | — |
MISSING_OUTPUT | Code calls ctx.output() for a missing node result. | { nodeId, iteration } |
DEP_NOT_SATISFIED | A typed dep on <Task> references an upstream output not yet produced. | { taskId, depKey, resolvedNodeId } |
BOUND_STALE | A <Task bind> authority row no longer matches the digest captured by ctx.prove(); the task parks while the run remains resumable. | { nodeId, bindings } |
ASPECT_BUDGET_EXCEEDED | An Aspects budget (tokens or latency) has been exceeded. | { kind, limit, current } |
APPROVAL_OUTSIDE_TASK | <Approval> is resolved outside the active task runtime. | — |
APPROVAL_OPTIONS_REQUIRED | An approval mode requiring explicit options is missing them. | — |
WORKFLOW_MISSING_DEFAULT | A workflow module does not export a default Smithers workflow. | — |
WORKFLOW_NOT_BUILT | A workflow’s default export is a raw component or JSX element instead of the object returned by smithers(...). | — |
Tools
| Code | When | Details |
|---|---|---|
TOOL_PATH_INVALID | A filesystem tool receives a non-string path. | — |
TOOL_PATH_ESCAPE | A filesystem tool resolves a path outside the sandbox root, including through symlinks. | — |
TOOL_FILE_TOO_LARGE | A read or edit operation exceeds the configured file size limit. | — |
TOOL_CONTENT_TOO_LARGE | A write operation exceeds the configured content size limit. | — |
TOOL_PATCH_TOO_LARGE | An edit patch exceeds the configured patch size limit. | — |
TOOL_PATCH_FAILED | A unified diff patch cannot be applied to the target file. | — |
TOOL_NETWORK_DISABLED | The bash tool tries to reach a non-loopback endpoint while network access is disabled. Loopback (localhost, 127.0.0.1, *.localhost, unix sockets) is always allowed. | Pass --allow-network (CLI) or allowNetwork: true (run options) when the command genuinely needs egress. |
TOOL_GIT_REMOTE_DISABLED | The bash tool attempts a remote git operation while network access is disabled. | Pass --allow-network (CLI) or allowNetwork: true (run options), or perform the remote operation outside the sandboxed tool. |
TOOL_COMMAND_FAILED | A bash tool command exits with a non-zero status. | — |
TOOL_GREP_FAILED | The grep tool fails with an rg execution error. | — |
Agents
| Code | When | Details |
|---|---|---|
AGENT_CLI_ERROR | A CLI-backed agent exits unsuccessfully, streams an explicit error, or its RPC transport fails. | — |
AGENT_QUOTA_EXCEEDED | An agent provider returns a usage-limit or quota error: transient, never consuming the retry budget. The task fails over to the next agent in its agent={[...]} chain, and the run only pauses (waiting-quota) once every agent in the chain is rate-limited, then until the earliest reset time among them. | { agentId?, agentEngine?, agentModel?, quotaResetAtMs?, resetHint? } |
AGENT_CONFIG_INVALID | A CLI-backed agent fails with a non-retryable configuration error such as an unknown model, missing LLM, or unsupported model. | — |
AGENT_RPC_FILE_ARGS | Pi RPC mode is used with file arguments the transport doesn’t support. | — |
AGENT_BUILD_COMMAND | An agent implementation forbids buildCommand() because it uses a custom generate() transport. | — |
AGENT_DIAGNOSTIC_TIMEOUT | An internal agent diagnostic check exceeds the per-check timeout budget. | — |
Database
| Code | When | Details |
|---|---|---|
DB_MISSING_COLUMNS | A table used by Smithers doesn’t expose required columns such as runId or nodeId. | — |
DB_REQUIRES_BUN_SQLITE | The database adapter is not backed by a Bun SQLite client with exec(). | — |
DB_QUERY_FAILED | A database read query throws or rejects while running inside an Effect. | — |
DB_WRITE_FAILED | A database write or migration fails, including after SQLite retry exhaustion. | — |
PG_POOL_SATURATED | Every connection in the shared PostgreSQL pool stayed busy for a full acquire wait, so the bound is too low for the concurrent workflows or a query is leaking a client. | { identity, max, maxSource, acquireTimeoutMs, totalCount, idleCount, waitingCount, configKnob } |
SMITHERS_BACKEND_CONFLICT | Multiple Smithers backend stores contain run history and no migrated.json receipt explains the divergence. | { populatedBackends, stores } |
SMITHERS_MIGRATION_REQUIRED | A physical Smithers store has run data but the resolved backend points elsewhere, so the history stays invisible until you migrate or pin the existing backend. | { sourceBackend, targetBackend, dbPath?, location?, runCount, schemaVersion, resolvedBackend? } |
STORAGE_ERROR | A storage service operation fails before surfacing a more specific database code. | — |
Migration errors
smithers migrate preserves the source SQLite store by default: if the legacy
smithers.db can’t be copied into the target backend, Smithers leaves the
original file untouched and reports the first actionable failure.
Corrupt, malformed, encrypted, or non-SQLite source files surface as
DB_QUERY_FAILED with the source dbPath in details; the message tells you
to verify the file with sqlite3 <dbPath> 'PRAGMA integrity_check' and restore
from backup or start fresh if SQLite confirms corruption.
Source files that exist but can’t be opened also surface as DB_QUERY_FAILED,
pointing at common operational causes: another process holding the file,
unreadable permissions, or a copied SQLite file missing its smithers.db-wal /
smithers.db-shm sidecars.
For Postgres migrations, smithers migrate --to postgres validates the target
connection string before opening the source store: a missing --url,
SMITHERS_POSTGRES_URL, or DATABASE_URL fails fast with INVALID_INPUT, so
connection setup problems aren’t hidden behind unrelated source-store errors.
Effect / Runtime
| Code | When | Details |
|---|---|---|
INTERNAL_ERROR | An unexpected internal exception crossed an Effect boundary without a more specific Smithers code. | — |
PROCESS_ABORTED | A spawned child process is aborted by signal or shutdown. | { command, args, cwd } |
PROCESS_TIMEOUT | A spawned child process exceeds its total timeout. | { command, args, cwd, timeoutMs } |
PROCESS_IDLE_TIMEOUT | A spawned child process stops producing output longer than its idle timeout. | { command, args, cwd, idleTimeoutMs } |
PROCESS_SPAWN_FAILED | The runtime cannot spawn the requested child process. | { command, args, cwd } |
TASK_RUNTIME_UNAVAILABLE | Builder task runtime APIs are accessed outside an executing step. | — |
SINGLE_RUNNER_BUSY | closeSingleRunnerRuntime() was called while a run or a task dispatch still holds the process-local SingleRunner runtime. The runtime is left open and usable; await the outstanding runs and close again. | { state, runIds, executionIds } |
SINGLE_RUNNER_CLOSED | A run or task dispatch tried to start after closeSingleRunnerRuntime() began. Call reopenSingleRunnerRuntime() to allow the runtime to be rebuilt lazily. | { state, operation } |
Hot Reload
| Code | When | Details |
|---|---|---|
SCHEMA_CHANGE_HOT | Hot reload detects a schema change requiring a full restart. | — |
HOT_OVERLAY_FAILED | Building or cleaning the generated hot-reload overlay fails. | — |
HOT_RELOAD_INVALID_MODULE | A hot-reloaded workflow module doesn’t export a valid default workflow build. | — |
Scorers
| Code | When | Details |
|---|---|---|
SCORER_FAILED | A scorer throws or rejects while Smithers is evaluating a result. | — |
CLI
| Code | When | Details |
|---|---|---|
INVALID_EVENTS_OPTIONS | The smithers events command receives invalid filter options. | — |
WORKFLOW_FILE_NOT_FOUND | A path-like workflow argument does not exist at the path resolved from the operator working directory. | { given, resolved } |
WORKFLOW_EXISTS | The workflow creation CLI refuses to overwrite an existing workflow file. | — |
CLI_DB_NOT_FOUND | A CLI command cannot find a nearby smithers.db file. | — |
CLI_AGENT_UNSUPPORTED | The ask command selects an agent integration Smithers doesn’t support in that mode. | — |
LISTENER_CREDENTIALS_MISSING | A declared listener workflow names a webhook secret environment variable that is not set. | workflow, secretEnv |
LISTENER_RECONCILE_FAILED | The listeners CLI encounters an unexpected reconciliation failure outside a typed integration error. | — |
Integrations
| Code | When | Details |
|---|---|---|
PI_HTTP_ERROR | The Pi or server integration receives a non-success HTTP response from Smithers. | — |
EXTERNAL_BUILD_FAILED | An external workflow host fails to build a Smithers HostNode payload. | { scriptPath, error?, exitCode?, stderr?, stdout? } |
SCHEMA_DISCOVERY_FAILED | External workflow schema discovery fails or returns invalid output. | { scriptPath, error?, exitCode?, stderr? } |
OPENAPI_SPEC_LOAD_FAILED | An OpenAPI spec cannot be loaded or parsed. | — |
OPENAPI_OPERATION_NOT_FOUND | The requested operationId doesn’t exist in the OpenAPI spec. | — |
OPENAPI_TOOL_EXECUTION_FAILED | An OpenAPI tool call fails during HTTP execution. | — |
ACCOUNT_INVALID | An account entry, label, provider, or provider-specific configuration is invalid. | — |
ACCOUNT_NOT_FOUND | An account operation references an unregistered label. | — |
ACCOUNT_DUPLICATE_LABEL | An account add operation would create a duplicate label without replace enabled. | — |
ACCOUNTS_FILE_INVALID | The accounts.json file isn’t valid JSON, or doesn’t match the expected account registry schema after tolerant entry filtering. | — |
INTEGRATION_ERROR | An integration source, delivery, or listener reconciliation operation fails. Listener reasons include credentials-missing, permission-denied, and listener-conflict. | reason, provider-specific safe details |
"provider": "gemini" subscription is skipped with a warning naming the
account label and valid providers, while the remaining valid accounts still
load. Such an entry is left out of the active account list but preserved
verbatim in accounts.json across later agents add and agents remove calls,
so an unrelated change never destroys the credentials its configDir points at.
Run bunx smthrs agents remove <label> to delete it, or
bunx smthrs agents add --label <label> --replace ... to migrate
it onto a supported provider.
ACCOUNTS_FILE_INVALID is reserved for invalid JSON or entries whose
known provider shape is malformed.
HTTP API Errors
JSON response codes, notSmithersErrorInstance objects.
| Code | Status | When |
|---|---|---|
INVALID_REQUEST | 400 | Invalid request body or query params |
PAYLOAD_TOO_LARGE | 413 | Body exceeds maxBodyBytes |
INVALID_JSON | 400 | Body not valid JSON |
SERVER_ERROR | 500 | Unexpected server error |
UNAUTHORIZED | 401 | Missing or invalid auth token |
UNTRUSTED_PROXY_PEER | 403 | mode: "trusted-proxy" request or WebSocket upgrade whose transport peer is not in auth.trustedProxies. The identity headers are discarded, not honored. See Gateway auth. |
WORKFLOW_PATH_OUTSIDE_ROOT | 400 | Workflow path outside server root |
RUN_ID_REQUIRED | 400 | runId required when resume: true |
RUN_ALREADY_EXISTS | 409 | Run ID already exists |
RUN_NOT_FOUND | 404 | No run with given ID |
RUN_NOT_ACTIVE | 409 | Run not active (cannot cancel) |
CONFLICT | 409 | Gateway launchRun was given a runId that an existing run already holds. Ownership never widens an id: a run id taken by another tenant conflicts the same way one of your own does. See Gateway run ownership. |
NOT_FOUND | 404 | Route or resource not found |
DB_NOT_CONFIGURED | 400 | Server database not configured |