Skip to content

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; transport?: string; phase?: string; artifactPath?: string; recovery?: 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 };

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. backendFailed() also accepts an optional context object (transport, phase, artifactPath, recovery) that carries transport-aware failure detail; describeRuntimeError() renders any variant into one diagnostic line.

export function unsupportedFeature(feature: string, reason: string): RuntimeError;
export function backendFailed(
backend: BackendTag,
message: string,
context?: { transport?: string; phase?: string; artifactPath?: string; recovery?: 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;
// Not a constructor — renders any RuntimeError into one human-readable
// diagnostic line, leading with backend/transport/phase/artifact context.
export function describeRuntimeError(error: unknown): string;

This table is the operation→tag map. When you pattern-match a Result’s error, the _tag tells you what kind of operation failed.

_tagProduced byTypical recovery
NothingToCommitgit.commit() when the index is emptySkip the commit; not an error condition for the caller.
BranchAlreadyExistsgit branch creation when the branch existsReuse the existing branch or pick a new name.
PushRejectedgit push rejected by the remoteRebase or force-with-lease after reviewing the remote.
CommandFailedCommandTool.run() and shell subprocessesInspect command/exitCode/stderr; retry or surface.
StructuredOutputValidationFailedStructured-output parsing when the schema rejectsRe-prompt or fall back; issues lists the violations.
UnsupportedFeatureFeature gates that refuse a request (unsupportedFeature())The request is not supported in this configuration — do not retry as-is.
BackendFailedAn LLM backend returned a fatal error (backendFailed())Check backend/message; may be transient — retry with backoff.
TypecheckFailedThe typecheck runner after a code changeRead stderr, fix the typing errors, re-run.
FileSystemErrorFsTool, snapshot store, and file sinks on IO failureCheck path/message; permissions, missing dir, disk.
IoFailedLoop source/sink/tool seams (ioFailed())seam says where (source/sink/tool), kind the adapter; often retried by the loop supervisor.

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
}
}
  • RuntimeError values are plain data — constructing one does not throw or perform IO. The throw/return boundary is the caller’s: tools return Result<_, RuntimeError> rather than throwing.
  • _tag is 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 _tag and read fields inside the matched arm.
  • BackendTag on BackendFailed is one of claude, codex, opencode, pi (see Backend Matrix).