Runtime Errors
Orca failures flow through a single discriminated union, RuntimeError, carried in the E channel of every Result (see Errors and Results). Because the error type is a closed set of tagged variants, agents and humans can pattern-match exhaustively on error._tag instead of string-matching messages.
RuntimeError is a z.discriminatedUnion("_tag", ...) defined in src/model/schemas.ts. Its members:
type RuntimeError = | { _tag: "NothingToCommit" } | { _tag: "BranchAlreadyExists"; branch: string } | { _tag: "PushRejected"; remote?: string; stderr: string } | { _tag: "CommandFailed"; command: string; exitCode: number | null; stdout: string; stderr: string } | { _tag: "StructuredOutputValidationFailed"; issues: readonly string[]; raw: unknown } | { _tag: "UnsupportedFeature"; feature: string; reason: string } | { _tag: "BackendFailed"; backend: BackendTag; message: string } | { _tag: "TypecheckFailed"; stdout: string; stderr: string; exitCode: number | null } | { _tag: "FileSystemError"; path: string; message: string } | { _tag: "IoFailed"; seam: "source" | "sink" | "tool"; kind: string; message: string };Constructors
Section titled “Constructors”Five variants have dedicated constructor functions in src/model/errors.ts. The remaining five (NothingToCommit, BranchAlreadyExists, PushRejected, TypecheckFailed, FileSystemError) are constructed inline at their call sites as tagged object literals.
export function unsupportedFeature(feature: string, reason: string): RuntimeError;export function backendFailed(backend: BackendTag, message: string): RuntimeError;export function commandFailed(args: { command: string; exitCode: number | null; stdout: string; stderr: string;}): RuntimeError;export function structuredOutputValidationFailed(args: { issues: readonly string[]; raw: unknown;}): RuntimeError;export function ioFailed(seam: "source" | "sink" | "tool", kind: string, message: string): RuntimeError;Which operations produce which tags
Section titled “Which operations produce which tags”This table is the operation→tag map. When you pattern-match a Result’s error, the _tag tells you what kind of operation failed.
_tag | Produced by | Typical recovery |
|---|---|---|
NothingToCommit | git.commit() when the index is empty | Skip the commit; not an error condition for the caller. |
BranchAlreadyExists | git branch creation when the branch exists | Reuse the existing branch or pick a new name. |
PushRejected | git push rejected by the remote | Rebase or force-with-lease after reviewing the remote. |
CommandFailed | CommandTool.run() and shell subprocesses | Inspect command/exitCode/stderr; retry or surface. |
StructuredOutputValidationFailed | Structured-output parsing when the schema rejects | Re-prompt or fall back; issues lists the violations. |
UnsupportedFeature | Feature gates that refuse a request (unsupportedFeature()) | The request is not supported in this configuration — do not retry as-is. |
BackendFailed | An LLM backend returned a fatal error (backendFailed()) | Check backend/message; may be transient — retry with backoff. |
TypecheckFailed | The typecheck runner after a code change | Read stderr, fix the typing errors, re-run. |
FileSystemError | FsTool, snapshot store, and file sinks on IO failure | Check path/message; permissions, missing dir, disk. |
IoFailed | Loop source/sink/tool seams (ioFailed()) | seam says where (source/sink/tool), kind the adapter; often retried by the loop supervisor. |
Pattern-matching failures
Section titled “Pattern-matching failures”Because RuntimeError is discriminated on _tag, a switch is exhaustive at the type level. Within a flow, errors arrive in the E channel of a Result; within a loop run, they are wrapped as LoopRunError = RuntimeError | TerminationContractError (see Loop API).
import { git } from "@twelvehart/orcats";
const result = await git().commit("ship it");if (result.isErr()) { switch (result.error._tag) { case "NothingToCommit": // not an error for us — nothing staged break; case "CommandFailed": console.error(result.error.stderr); break; // ...remaining tags — the compiler flags any you miss }}Side effects and invariants
Section titled “Side effects and invariants”RuntimeErrorvalues are plain data — constructing one does not throw or perform IO. The throw/return boundary is the caller’s: tools returnResult<_, RuntimeError>rather than throwing._tagis the discriminant and is stable across versions; the payload fields may grow but existing fields are not renamed without a major version. When matching, branch on_tagand read fields inside the matched arm.BackendTagonBackendFailedis one ofclaude,codex,opencode,pi(see Backend Matrix).