Skip to content

Reference: errors

Status: Implemented in v0.1. Stability: experimental (codes are intended to be stable from 0.1 onward; additions allowed, meanings never repurposed).

Package: @orpc-agent/core. Concepts and rationale: concepts/errors.md.

CapabilityError

ts
class CapabilityError extends Error {
  readonly code: ErrorCode;          // stable machine-readable identifier
  readonly stage: FailureStage;      // where in the pipeline it failed
  readonly publicMessage: string;    // model-safe; the ONLY message adapters may forward
  readonly retryable: boolean;       // may an identical request succeed later?
  readonly exposeToModel: boolean;   // gate for code+publicMessage vs generic shape
  readonly details?: unknown;        // model-safe structured data (e.g. validation issues)
  readonly cause?: unknown;          // private diagnostic; NEVER serialized outward
}

message (inherited) equals publicMessage plus code — safe to log. cause carries the original thrown error for server-side logs only (SI-9).

ts
type FailureStage =
  | "discovery" | "input-validation" | "authentication" | "authorization"
  | "policy" | "approval" | "execution" | "output-validation"
  | "timeout" | "cancellation" | "adapter";

Error codes

CodeStageRetryableexposeToModelProduced when
CAPABILITY_NOT_FOUNDdiscoverynoyesUnknown id, unexposed surface, or hidden by policy — externally indistinguishable (SI-8)
INPUT_INVALIDinput-validationnoyesInput fails the input schema; details carries issue paths/messages
UNAUTHENTICATEDauthenticationnoyesMissing/malformed actor, or oRPC UNAUTHORIZED from middleware
FORBIDDENauthorizationnoyesoRPC FORBIDDEN from middleware / app authorization
POLICY_DENIEDpolicynoyesA policy returned deny; public message from the decision
POLICY_FAILEDpolicynonoA policy threw or timed out — fail closed (SI-7)
APPROVAL_REQUIREDapprovalnoyesOnly via unwrap()/adapter translation of the approval-required envelope
APPROVAL_PENDINGapprovalyesyesresume() before a decision exists
APPROVAL_REJECTEDapprovalnoyesResume after rejection
APPROVAL_EXPIREDapprovalnoyesResume after expiry; a new request is needed
APPROVAL_CONSUMEDapprovalnoyesResume of an already-executed approval (single-use, SI-5)
APPROVAL_INPUT_MISMATCHapprovalnonoStored input no longer matches its hash — integrity failure
APPROVAL_SELF_APPROVALapprovalnoyesApprover identity equals requester (SI-4)
APPROVAL_UNSERIALIZABLE_INPUTapprovalnonoApproval required but validated input not canonically serializable
EXECUTION_FAILEDexecutionvariesvariesHandler threw. oRPC type-safe errors: exposeToModel=true with their message. Unexpected throws: exposeToModel=false, generic public message
OUTPUT_INVALIDoutput-validationnonoHandler output violates output schema; output withheld
TIMEOUTtimeoutyesyesComposite signal fired from the timeout timer
CANCELLEDcancellationnoyesComposite signal fired from the caller's signal
AUDIT_UNAVAILABLEexecutionyesnoaudit.strict and the capability.started sink write failed
ADAPTER_ERRORadapternonoProtocol translation failure inside an adapter
INTERNAL_ERROR(any)noRuntime invariant violation; also the generic serialized shape for any non-exposed error

Notes:

  • retryable describes whether an identical request could succeed later; it feeds runtime retry eligibility and the model-visible flag. EXECUTION_FAILED retryability comes from the thrown error when it is a CapabilityError, else false.
  • TIMEOUT is retryable but still subject to SI-11: the runtime will not auto-retry a timed-out write.

Adapter serialization

The only two shapes a model client can ever receive:

jsonc
// exposeToModel === true
{ "code": "POLICY_DENIED", "message": "Refunds above $500 require manager approval.", "retryable": false }

// exposeToModel === false — regardless of the real code
{ "code": "INTERNAL_ERROR", "message": "The operation failed.", "retryable": false }

details is included only for INPUT_INVALID (validation issues) — it is data the model itself produced. stage, cause, and stack traces never cross the adapter boundary (SI-9). Full model-visible mapping per adapter: adapters/ai-sdk.md, adapters/mcp.md.

Relationship to oRPC errors

Inside a handler you keep using oRPC's error system (.errors(), errors.NOT_ELIGIBLE(), ORPCError). The runtime maps them at the boundary (pipeline stage 11):

Thrown in procedureBecomes
Type-safe error from .errors()EXECUTION_FAILED, exposeToModel=true, its message as publicMessage
ORPCError("UNAUTHORIZED")UNAUTHENTICATED (stage authentication)
ORPCError("FORBIDDEN")FORBIDDEN (stage authorization)
Other ORPCErrorEXECUTION_FAILED, exposeToModel=true, message preserved
Any other throwEXECUTION_FAILED, exposeToModel=false, cause preserved privately

Design intent: errors you declared are contract, safe for models to see and react to; errors you didn't are internals.

Independent community project — not affiliated with or endorsed by the oRPC maintainers.