Skip to content

08. Error Codes ​

Source of truth: packages/shared/src/errors.ts (ErrorCodes). Codes in §3.7 are reserved (documented ahead of emission); everything else is live.

1. Goal ​

Provide one stable error vocabulary across:

  • Renderer UI
  • Electron IPC
  • Rust host RPC
  • Node pi sidecar bridge

2. Error object ​

ts
type AppError = {
  code: string            // stable machine code, e.g. TOOL_DENIED
  message: string         // English UI/default message
  details?: unknown
  retriable?: boolean
  source?: "renderer" | "electron" | "host" | "agent" | "plugin"
  causeCode?: string      // nested/transport code if mapped
  traceId?: string
}

Rules:

  1. code is immutable once published
  2. message is English source text (i18n key may map separately)
  3. UI should prefer i18n key derived from code when available

The desktop test suite (apps/desktop/test/error-code-registry.test.mjs) verifies that every ErrorCodes entry appears in this document and that every errorCode host-core emits from its RPC dispatcher and native tools is registered; reserved codes in §3.7 remain intentionally absent from ErrorCodes until an implementation emits them.

3. Code registry ​

3.1 App / protocol ​

coderetriablemeaning
PROTOCOL_MISMATCHnohandshake/protocol version mismatch
HOST_UNAVAILABLEyesRust host not running/reachable
HOST_OVERLOADEDyesbounded host RPC/tool capacity is full; retry after backpressure
AGENT_UNAVAILABLEyespi sidecar not running/reachable
APP_DEGRADEDyesapp running with limited capabilities
INTERNALmaybeunexpected internal failure
INVALID_ARGUMENTnorequest schema/args invalid, including a native-tool path of the wrong file/directory kind
INVALID_PARAMSnohost-core RPC parameter validation failed (numeric 1002); the sidecar and renderer surface it unchanged
UNAUTHORIZEDnocapability/auth boundary rejected call
NOT_FOUNDnoentity not found
SESSION_NOT_FOUNDnoa session-scoped RPC (including tools.execute) named a session the host does not have; an unknown id never inherits the global workspace
CONFLICTmaybestate conflict / busy resource
UNSUPPORTEDnothe operation has no implementation on this surface, e.g. a trusted-extension prompt while no desktop window can show it (spec 16 §9)
FORBIDDENnoRACP: the principal's roles do not admit the method (spec 19 §13)
METHOD_NOT_FOUNDnoRACP: unknown method on this Host, e.g. host/list without a Gateway
IDEMPOTENCY_CONFLICTnoa queue or turn idempotency key was reused with different input (D386)
CURSOR_EXPIREDnoRACP: the replay cursor is older than the retained window; resubscribe from a snapshot
CLIENT_TOO_SLOWyesRACP: the client fell behind the event stream and was disconnected
APPROVAL_EXPIREDnoRACP: the approval deadline passed before an answer arrived
APPROVAL_STALEnoRACP: the approval was already settled or belongs to an older turn
PAYLOAD_TOO_LARGEnoRACP: a frame exceeded the negotiated size bound
TIMEOUTyesgeneric timeout
NETWORK_POLICY_BLOCKEDnothe main-process public-network guard refused a fetch because it judged the target: the URL failed the syntactic public-HTTPS check, or the local DNS lookup returned an address the policy classifies as non-public — including a fake-IP placeholder a local proxy invented (ADR 0243). A desktop-only code; a refusal is a verdict, so retrying cannot succeed until the address changes. A resolver that returned no answer at all is NETWORK_RESOLVE_FAILED instead (issue #419). Since ADR 0304 an endpoint the user typed themselves may resolve to their own loopback or LAN, so this code now reports a first hop only for the classes that name no service at all (cloud metadata, unspecified, multicast, reserved) or for a third-party hop — a redirect target, a catalog body, a registry record.
NETWORK_RESOLVE_FAILEDyesthe main-process public-network guard could not classify the target host: the local DNS lookup returned no answer, or threw before returning one. The request is refused exactly as a policy refusal is, but no address was judged, so no page or log may report it as an address-check decision. Distinct from NETWORK_ERROR, which is a failure of the request itself. Retriable: a resolver or proxy that starts answering the same host makes the same request succeed (ADR 0243, issue #419).
HOST_SHUTTING_DOWNyesthe host received EOF and is draining; the call was refused rather than started
RATE_LIMITEDyesa per-caller host budget (plugin session import, batch operations) was exceeded inside its window
LIMIT_EXCEEDEDnoa payload exceeded a fixed host bound (item count, byte size, or a 64 MiB NDJSON request line) and was refused
CONFIG_SYNC_INVALIDnoinvalid sync configuration, password, path, request, or approval input
CONFIG_SYNC_LOCKEDnothe local encrypted sync vault is not unlocked
CONFIG_SYNC_UNSUPPORTEDnothe vault format or WebDAV server capability is unsupported
CONFIG_SYNC_REMOTEmayberemote WebDAV object, authentication, quota, or availability failure
CONFIG_SYNC_CONFLICTmayberemote head, vault identity, or approval digest conflict
CONFIG_SYNC_CRYPTOnoauthenticated encryption, object identity, or ciphertext validation failed
CONFIG_SYNC_MAPPING_REQUIREDnoimported project-scoped configuration needs an explicit local folder/group mapping
CONFIG_SYNC_LIMIT_EXCEEDEDnoencrypted sync state exceeded an entity, object, resource, archive, or decompression bound

HOST_UNAVAILABLE is reserved for a missing or broken host process/transport, not ordinary admission pressure. RPC capacity returns HOST_OVERLOADED, and an admitted shell that cannot start because the OS is temporarily out of process resources returns PROCESS_RESOURCE_EXHAUSTED. Host-core's control stdio is isolated from Tokio's dynamic blocking pool so the latter condition does not turn temporary thread pressure into a host process exit.

3.2 Agent / session ​

coderetriablemeaning
AGENT_BUSYnosession already has active turn; leftover subagents after a terminal parent error do not keep the session busy (D352)
AGENT_NOT_FOUNDnosession missing
TURN_NOT_FOUNDnoturn id invalid
TURN_ABORTEDnoturn aborted by user/system
MODEL_NOT_CONFIGUREDnono usable model selected, or provider rejects the selected model as unknown
PROVIDER_ERRORyesupstream provider failure; a retryable one (5xx gateway) gets up to ten same-turn retries, a malformed 400/422 request is terminal
PROVIDER_UNAUTHORIZEDnobad/missing provider credentials
PROVIDER_RATE_LIMITEDyesprovider rate limited; runtime silently retries up to ten times across setup/stream before the terminal event
CONTEXT_TOO_LARGEnoprompt/context still exceeds the safe model budget after recovery, the second provider overflow occurred, or automatic recovery is disabled
CONTEXT_COMPACTION_FAILEDnoautomatic retained-tail recovery could not prepare, persist, or fit a checkpoint, or manual checkpoint summary generation / durable append failed; the guarded next provider request does not start
STREAM_FAILEDyesprovider stream was terminated, closed prematurely, or otherwise ended before a complete response; up to ten same-turn retries may precede the terminal event
EMPTY_MODEL_RESPONSEyesthe model ended its turn with no tool call and no visible text twice: once as streamed, once after the automatic re-run; the first reply to a Host-ledger completion notice is exempt (spec 02-agent-runtime §5e, D446)
PROMPT_ENHANCEMENT_EMPTYnothe one-shot enhancement model returned no text
SPEECH_NOT_CONFIGUREDnohost speech ASR or TTS is not bound in settings
SPEECH_PROTOCOL_UNSUPPORTEDnothe speech protocol is unknown or does not support this role
SPEECH_INPUT_TOO_LARGEnospeech input exceeds 25 MB
SUBAGENT_IDLE_TIMEOUTnowithdrawn (D328): idle watchdogs are not armed; the code remains for stored results
SUBAGENT_DURATION_TIMEOUTnowithdrawn (D328): duration watchdogs are not armed; the code remains for stored results
SUBAGENT_CONTEXT_OVERFLOWnoa delegate's own model context exceeded its safe budget and neither automatic turn-boundary compaction nor the degraded retry that keeps only the task brief and the most recent messages brought it back below the limit; the failure names the actionable recovery instead of the provider's overflow text

3.3 Workspace / tools / permissions ​

coderetriablemeaning
WORKSPACE_REQUIREDnono workspace bound
PATH_OUTSIDE_WORKSPACEnopath escapes sandbox before an explicit outside-path permission decision, or a prompt attachment is outside its session scratch/project/attachment roots
WORKSPACE_PATH_DENIEDnoan explicit Read/Write/Edit path hit the always-on security denylist (private keys, .env files, credential bundles, .git/objects); an outside-path grant does not lift it (spec 15 §3)
READ_PATH_IS_DIRECTORYnoRead was given a directory; the result carries a Glob suggestion
TOOL_BINARY_CONTENTnoRead refused to dump a binary file into the model context
TOOL_NOT_FOUNDnounknown tool
TOOL_DENIEDnopermission denied / mode forbidden
TOOL_TIMEOUTyestool execution timeout
TOOL_FAILEDmaybetool executed but failed
TOOL_ABORTEDnothe tool was cancelled by a user stop or a turn abort before it finished
MUTATION_RETRY_BUDGET_EXHAUSTEDyesthe repeat guard ended the turn after same-path Edit or shell patch failures; carries details.kind (edit or patch-command), the last tool error code, and a class-specific details.recovery hint
PROCESS_RESOURCE_EXHAUSTEDyesshell process could not start because the OS temporarily exhausted process resources
SHELL_NOT_FOUNDnono effective platform shell is available after catalog fallback; message carries guidance
COMMAND_SHELL_CHANGEDnopinned shell ID or dialect changed before execution
COMMAND_SHELL_INVALIDnosettings supplied an unknown, unavailable, or wrong-platform shell ID
PERMISSION_TIMEOUTnopermission prompt timed out (mapped to deny)
PERMISSION_REQUIREDnowaiting for user decision
WRITE_DISABLED_IN_PLANnocontract-mode hard-deny for Write
EDIT_DISABLED_IN_PLANnocontract-mode hard-deny for Edit
PLUGIN_DISABLED_IN_PLANnocontract-mode hard-deny for every plugin tool
TOOL_DISABLED_IN_PLANnocontract-mode hard-deny for an unknown/unlisted tool
PLAN_NOT_ACTIVEnoa submit tool ran while no contract was being negotiated
PLAN_KIND_MISMATCHnoSubmitPlan in Goal mode, or SubmitGoal in Plan mode
PLAN_APPROVAL_REQUIREDnoSubmitPlan/SubmitGoal is waiting for a separate approval
PLAN_APPROVAL_TIMEOUTnoabsolute 30-minute plan approval deadline expired
PLAN_APPROVAL_STALEnoresponse does not match the live proposal/session/turn/tool-call/version
PLAN_APPROVAL_INTERRUPTEDnopending approval closed during abort, crash, or persistence failure
PLAN_ARTIFACT_WRITE_FAILEDnohost could not write exact bytes to a new .pi/<kind>/*.md artifact
PLAN_EXECUTION_INTERRUPTEDnoapproved queued/running Plan or Goal execution stopped without replay
PLAN_REQUIRES_INTERACTIVE_SESSIONnounattended/scheduled Plan or Goal run cannot request approval
PLAN_NOT_FOUNDnono approval row matches the proposal id
PLAN_SESSION_NOT_FOUNDnothe Plan/Goal RPC named a session the host does not have
PLAN_WORKSPACE_REQUIREDnothe session has no persisted project; temporary sessions cannot enter Plan or Goal
PLAN_ALREADY_ACTIVEnothe session already has a contract being negotiated
PLAN_ALREADY_PENDINGnoa submit arrived while an approval for the same turn is still pending
PLAN_ALREADY_RESOLVEDnoa second approve/reject reached an already-resolved approval
PLAN_APPROVAL_CONFLICTnothe approval row changed underneath a version-guarded update
PLAN_INVALID_ACTIONnothe approval response is neither approve nor reject
PLAN_INVALID_ARGUMENTnosubmit/resolve arguments failed validation
PLAN_PERMISSION_MODE_REQUIREDnoapprove did not select ask, accept-edits, or auto
PLAN_PERMISSION_MODE_INVALIDnothe selected permission mode is not one of the three
PLAN_MARKDOWN_TOO_LARGEnothe submitted Markdown exceeds the artifact size bound
PLAN_REJECTEDnothe user rejected the proposal; the turn ends without execution
PLAN_SUBMIT_FAILEDmaybethe host could not record the proposal
PLAN_CONFIGURATION_BLOCKEDnosession.configure was refused while a proposal or execution is live
PLAN_ARTIFACT_INVALIDnothe checkpoint artifact failed validation before execution
PLAN_ARTIFACT_NOT_READYnoexecution was claimed before the artifact was durably written
PLAN_ARTIFACT_PATH_UNSAFEnothe artifact path escaped <workspaceRoot>/.pi/<kind>/
PLAN_ARTIFACT_COLLISION_LIMITnothe host ran out of unique artifact names
PLAN_ARTIFACT_HASH_MISMATCHnoartifact bytes no longer match the recorded hash at execution time
PLAN_EXECUTION_ACTIVEnoan approved execution is already running for the session
PLAN_EXECUTION_NOT_FOUNDnono queued execution matches the claim
PLAN_EXECUTION_ALREADY_CLAIMEDnoanother claimant took the queued execution first
PLAN_EXECUTION_STALEnothe execution epoch no longer matches the live session
PLAN_EXECUTION_STATUS_INVALIDnoa status transition was not allowed from the current state
PLAN_EXECUTION_CONFLICTnothe execution row changed underneath a version-guarded update
PLAN_EXECUTION_FAILEDmaybethe approved execution ended in an error
PLAN_INTERNALmaybea Plan/Goal host failure with no finer classification
WRITE_DISABLED_IN_CHATnohistorical (pre-D188 Chat profile); registered for stored transcripts, no longer emitted
BASH_DISABLED_IN_CHATnohistorical (pre-D188 Chat profile); registered for stored transcripts, no longer emitted

The _IN_PLAN suffix and the PLAN_ prefix are historical: both contract modes (Plan and Goal) share these codes rather than duplicating a _IN_GOAL set (D198). The renderer picks its wording from the proposal's kind, so one code can surface as either "Plan" or "Goal" copy.

3.4 Edit contract (ADR 0087) ​

Emitted only by Edit. Version and provenance failures have their own codes because each names a different next action; reporting them as TOOL_FAILED loses that. See 18-line-anchored-edit-contract §11.

coderetriablemeaning
EDIT_TAG_REQUIREDnotag missing or not 4 hex digits
EDIT_TAG_MISMATCHyes after a Readtag does not hash the live file and drift recovery declined; carries the live tag and current content at the anchors
EDIT_TAG_UNKNOWNyes after a Readtag is well-formed but the session recorded no such content for the path
EDIT_LINES_UNSEENyesanchors reference lines the session never displayed; carries the revealed content
EDIT_PARSE_FAILEDnomalformed op header, body row under a colonless header, missing body, or a -/context row; the host message identifies the required syntax when possible
EDIT_RANGE_INVALIDnoreversed range, out-of-bounds line, overlapping ops, or duplicate anchor
EDIT_BLOCK_UNRESOLVEDnoa N* locator did not resolve; message names the plain-range alternative
EDIT_REGISTER_EMPTYnopaste from an unset register
EDIT_REGISTER_AMBIGUOUSnoanonymous paste with more than one pending anonymous capture
EDIT_REPAIR_AMBIGUOUSnoboundary-repair candidates tied at minimum cost
EDIT_NO_CHANGEnothe apply produced text identical to the input
EDIT_AMPLIFICATION_LIMITnolowering exceeded the expansion cap

EDIT_LINES_UNSEEN is retriable without a further Read when its message reports a complete reveal: the revealed lines are merged into the session's provenance, so the same tag retried unchanged applies. A truncated reveal merges nothing and requires the re-read.

EDIT_TAG_MISMATCH, EDIT_TAG_UNKNOWN, and EDIT_LINES_UNSEEN each get one free attempt per path before the repeat guard counts them, because each already carries what the retry needs. The remaining codes count on first occurrence, and the failure that exhausts the budget surfaces as §3.3's MUTATION_RETRY_BUDGET_EXHAUSTED on the assistant row (18-line-anchored-edit-contract §9.3). Its details.recovery value distinguishes syntax correction from the fresh-read path, so a follow-up does not blindly re-read a file when the payload itself is malformed.

3.5 Secrets / settings ​

coderetriablemeaning
PROVIDER_SECRET_MISSINGnoenabled provider requires an API key
MODEL_ALIAS_TOO_LONGnoconfigured model alias exceeds 60 Unicode characters
MODEL_BINDINGS_DEGRADEDnostored model bindings are unreadable; explicit model-array replacement is blocked to prevent data loss
SECRET_STORE_UNAVAILABLEmaybeOS secure storage unavailable (reserved)
SETTINGS_INVALIDnosettings payload invalid (reserved)

3.6 Plugins ​

coderetriablemeaning
PLUGIN_NOT_FOUNDnoplugin id missing
PLUGIN_INVALIDnomanifest/package invalid
PLUGIN_LOAD_FAILEDmaybeenable/load failed
PLUGIN_DISABLEDnoplugin disabled (reserved)
PLUGIN_PERMISSION_DENIEDnoplugin lacks the declared and granted permission the call needs
PLUGIN_INTEGRITYnopackage checksum or signature did not match the catalog entry
PLUGIN_NETWORKyesmarketplace download or catalog fetch failed
PLUGIN_HOST_TOO_OLDnothe package's engines.piDesktop range excludes this host
PLUGIN_MARKET_INVALIDnothe marketplace catalog is malformed or missing required release fields
PLUGIN_MARKET_UNTRUSTED_HOSTnothe catalog or package URL is outside the trusted marketplace hosts
PLUGIN_MARKET_YANKEDnothe requested release was withdrawn from the catalog
PLUGIN_MARKET_NOT_PUBLISHEDnothe platform has the version and is not offering it yet
PLUGIN_MARKET_ARCHIVEDnothe plugin was withdrawn from the platform
PLUGIN_MARKET_NOT_FOUNDnothe platform does not have that plugin or version
PLUGIN_MARKET_RATE_LIMITEDyesthe download endpoint asked the client to wait
PLUGIN_MARKET_NO_SOURCEmaybeno distribution target can serve the package
PLUGIN_CANCELLEDnothe user cancelled an install while it was downloading
MCP_INVALIDnoa user MCP server definition failed validation
SKILL_INVALIDnoa user skill document failed validation
SUBAGENT_INVALIDnoa user subagent document failed validation
CAPABILITY_INVALIDnoan agent capability root or scope setting failed validation
PLUGIN_COMMAND_NOT_FOUNDnocommand id missing (reserved)
PLUGIN_CRASHEDyesplugin runtime crashed (reserved)
PLUGIN_CONTRACT_MISMATCHnounsupported manifest/api version (reserved)

3.7 Reserved detail codes (not yet emitted) ​

Finer-grained provider/tool distinctions documented for future mapping. Until emitted, implementations use the canonical parent code shown.

reserved codecanonical parent todaynotes
PROVIDER_BASE_URL_INVALIDPROVIDER_ERRORendpoint invalid (400)
PROVIDER_PROTOCOL_MISMATCHPROVIDER_ERRORwrong protocol profile
PROVIDER_MODEL_NOT_FOUNDMODEL_NOT_CONFIGUREDunknown model id (404)
PROVIDER_TIMEOUTTIMEOUTnetwork/server timeout (retriable)
PROVIDER_UNSUPPORTED_CAPABILITYPROVIDER_ERRORtools/vision unsupported
PROVIDER_DISABLEDMODEL_NOT_CONFIGUREDprovider disabled

WORKSPACE_PATH_DENIED and TOOL_BINARY_CONTENT left this table when the host started emitting them (§3.3).

Historical aliases (never use in new code): PROVIDER_AUTH_FAILED → PROVIDER_UNAUTHORIZED; PROVIDER_STREAM_INTERRUPTED → STREAM_FAILED; WORKSPACE_OUTSIDE_ROOT → PATH_OUTSIDE_WORKSPACE; SECRET_MISSING → PROVIDER_SECRET_MISSING; SHELL_UNAVAILABLE → SHELL_NOT_FOUND; SHELL_IDENTITY_STALE → COMMAND_SHELL_CHANGED; PLAN_APPROVAL_EXPIRED → PLAN_APPROVAL_TIMEOUT. Truncation is not an error: a bounded tool result carries a marker naming which end survived and where the rest is, or reports the bounded window in sibling result fields (see 16-tool-result-limits).

3.8 Remote control (RACP-WS / SSH bootstrap) ​

Emitted by the desktop's remote-host client and the pi-host server when a session lives on a paired remote machine driven over RACP-WS (see 19-remote-agent-control-protocol, ../05-security/02-remote-control-security, ADR 0285). The renderer never sees the local/remote split beyond a badge; these codes surface through the same error object as any other call.

coderetriablemeaning
HOST_DISCONNECTEDyesthe remote host connection dropped; in-flight calls are rejected and the client reconnects and resubscribes by cursor
HOST_BOOTSTRAP_FAILEDnoprovisioning the remote pi-host over SSH failed (download, checksum mismatch, or install.sh); details.reason names the stage
HOST_VERSION_MISMATCHnothe remote pi-host version does not match the desktop; the desktop refuses to drive an incompatible host
REMOTE_AUTH_FAILEDnothe device or pairing token was rejected on the RACP-WS upgrade
REMOTE_CONNECTION_FAILEDyesthe RACP-WS transport could not connect (non-loopback URL, refused socket)
REMOTE_FORWARD_FAILEDyesthe SSH loopback port forward could not be established
REMOTE_PATH_NOT_FOUNDnoa remote project/workspace path does not exist on the host
REMOTE_PATH_FORBIDDENnoa remote path is outside the host's permitted roots
PAIRING_FAILEDnoconnection/pair could not mint a device credential
PAIRING_TOKEN_EXPIREDnothe single-use pairing token expired before pairing completed
CAPABILITY_UNAVAILABLEnoan operation was requested for a capability the host advertised as unavailable (e.g. attachments, tool relay)

4. Mapping rules ​

Host RPC numeric → AppError.code ​

See 06-host-rpc-protocol.md numeric table.
Example: host 1004 → TOOL_DENIED.

Provider exceptions ​

Node sidecar maps provider SDK errors into:

  • PROVIDER_UNAUTHORIZED
  • PROVIDER_RATE_LIMITED
  • MODEL_NOT_CONFIGURED (provider rejects the selected model with 404)
  • PROVIDER_ERROR
  • NETWORK_ERROR
  • STREAM_FAILED

An exact terminated provider message and equivalent premature stream-close messages map to STREAM_FAILED. A request-setup or post-response PROVIDER_RATE_LIMITED uses the shared runtime budget: ten retries after the initial attempt, with setup and stream failures counting together. Non-429 transient failures — STREAM_FAILED, NETWORK_ERROR, TIMEOUT, and retryable PROVIDER_ERROR such as an upstream gateway 502/503/504 — share their own bounded budget of ten retries after the initial attempt, also counted together across setup and stream, and separate from the 429 budget. Both budgets are abortable and reset after a complete successful model response, including a tool-call response, in both the main session and builtin subagents. Headers, partial output, and phase changes do not replenish them. Terminal exhaustion reports retryAttempt: 10 from the applicable budget even after retry activity cleanup. The 429 path honors retry-after-ms, retry-after seconds, and HTTP-date headers before client backoff and caps a wait at 30 seconds; the non-429 path applies the same precedence with an 8-second cap and otherwise waits 1, 2, 4, then remains at 8 seconds for later retries. Only the failed request is replayed; the session and its tool state are untouched. A non-retryable PROVIDER_ERROR from a malformed 400/422 request never enters either budget. The persisted infiniteProviderRetry setting is false by default; when true it removes only the retry-count ceiling for the admitted transient/network classes (including 429). Backoff, Retry-After, cancellation, and terminal classification remain unchanged, and the setting may continue API usage until the user stops the turn.

A NETWORK_ERROR carries the failing transport layer as bounded details: networkCategory (dns, tls, timeout, refused, unreachable, reset, proxy, or unknown when nothing survived), networkCode (the errno, e.g. ENOTFOUND, ECONNRESET, EPROTO, UND_ERR_SOCKET), and, when the transport reported them, networkSyscall and networkHost. Only the bare hostname is kept — never a URL, port, path, query, or credential — and providerCode is omitted when it would repeat networkCode. Per-layer codes (DNS_ERROR, TLS_ERROR, SOCKET_RESET, …) are deliberately not introduced: the category splits the layers without adding user-visible codes and locale strings for each of them.

The diagnosis is read from the live cause chain at the fetch boundary, not only from the provider message. pi-ai flattens a rejected request into errorMessage, so by the time classification runs the errno undici keeps in error.cause is already gone and a bare fetch failed can only be reported as networkCategory: unknown; the fetch wrapper still holds the original Error and supplies the same validated fields from it. A captured cause also settles the phase: the fault is reported as phase: request because no response ever arrived, which is what distinguishes it from a stream that ended mid-response. networkRoute (direct, environment-proxy, http-proxy, socks5-proxy) names the hop the request was taking, so a failure at the proxy is readable without guessing from an errno.

When one origin fails this way repeatedly inside a turn — twice in a row, without any response — the provider transport is rebuilt before the next attempt instead of replaying into the same undici pool. The rebuild is process-wide and deliberately bounded: one rebuild per streak, at most one every 30 seconds, and never for a dns failure, which a fresh pool cannot change. The replacement is installed before the previous dispatcher is closed, and the previous one is closed gracefully, so a request another session already dispatched finishes on the pool it started on. The route in effect is reproduced, never downgraded to a direct connection.

Permission timeout ​

UI/host timeout emits PERMISSION_TIMEOUT internally, tool result presented as denied (TOOL_DENIED) to agent.

Shell and Plan/Goal checkpoint failures ​

SHELL_NOT_FOUND is returned only when catalog fallback finds no available platform shell. COMMAND_SHELL_CHANGED never retries with a different shell; the turn must obtain a fresh effective ID/dialect. PLAN_ARTIFACT_WRITE_FAILED never creates an approval row. PLAN_APPROVAL_TIMEOUT applies only to the absolute pending deadline; PLAN_EXECUTION_INTERRUPTED identifies an already-approved queued/running execution interrupted by abort or host recovery. PLAN_KIND_MISMATCH is a terminating tool error like PLAN_NOT_ACTIVE: the submit tool ran against the wrong contract, so no artifact is written and no approval row is created.

Local request preparation failures ​

A structured LOCAL_REQUEST_ERROR from context validation, context estimation, or request preparation maps to the existing INTERNAL code with retriable: false. Preserve its local origin and phase before adapter errors are flattened to text. Diagnostics may retain the cause type, but must not copy request content, search results, credentials or arbitrary cause messages into the UI. Do not identify these failures by matching an exception sentence or by treating all JavaScript TypeErrors alike: fetch transport failures retain the existing network/retry and cancellation behavior.

Restored-history validation may fail before a runtime stream exists. In that case the existing RPC error data carries errorCode, retriable: false, and safe details (origin, phase, optional cause type). No provider request is made, the sidecar stays available, and a stored record is never rewritten. A container that is not a stored block list still fails this way.

A single stored block that cannot be replayed is a different case: this app itself stores display-only blocks when a gateway drops ids, so the whole stored replay for that message degrades to "no replay" instead of failing every later turn. The turn continues, display rounds are unchanged, and the diagnostic records the block count and phases without copying search content, results or credentials.

5. UI handling guidelines ​

classUI behavior
auth/config (PROVIDER_SECRET_MISSING, MODEL_NOT_CONFIGURED)assistant error message with settings CTA
permission denialsinline tool card state
retriable provider/networkassistant error message with diagnostic details and Continue; the session-scoped failed-turn recovery card is a fallback only when no structured assistant error is present
internal/host unavailabledegraded banner + recovery tip

Message-bound provider failures never use a toast or floating global banner. A PROVIDER_RATE_LIMITED failure remains invisible while its bounded retry budget is available; only exhaustion renders the assistant error and lifecycle error. The assistant error message shows a localized summary and stable code, with an accessible details disclosure containing the redacted provider response, provider ID, and model ID. Provider detail is capped at 600 characters and common credential/header values are redacted before event emission or persistence. When available, the details disclosure may also show bounded phase, providerStatus, providerCode, providerWaitMs, streamMs, retryAttempt, networkCategory, networkCode, networkSyscall, networkHost, networkRoute, requestMessages, requestBytes, and compactionGeneration fields. The request fields are counts and byte sizes only and the compaction field is the checkpoint generation counter; none of them carries message content. While a transient provider failure retries, the activity indicator's reason popover shows the localized summary, the stable code, and — for a network failure — the transport errno (NETWORK_ERROR · ENOTFOUND), so the failing layer is visible during the retry loop as well as in the log record. The assistant error card offers a localized Continue action that resends the continuation prompt (继续当前任务 / Continue the current task) in the same session without truncating the failed turn. The session-scoped failed-turn recovery card is used only when no structured assistant error is present; neither failure surface offers Regenerate.

6. i18n key convention ​

text
errors.<code>
errors.<code>.action

Examples:

  • errors.PROVIDER_SECRET_MISSING
  • errors.PROVIDER_SECRET_MISSING.action
  • errors.HOST_UNAVAILABLE

7. Acceptance ​

  1. Every IPC failure returns AppError.code
  2. No raw untyped string-only failures on main paths
  3. Plan/Goal hard-denies use explicit tool-specific codes; Bash is never denied by either contract mode solely because of the operating mode and instead follows permission policy
  4. Host numeric codes map to stable string codes
  5. Invalid shell settings, no-effective-shell/stale-pin, artifact-write, expiry, scheduled-rejection, and restart-interruption paths map to stable codes; only the documented pre-turn catalog fallback is allowed and no work is replayed

Certificate verification failures (issue #714) ​

NETWORK_ERROR is non-retriable when details.networkCode is a recognized certificate verification failure, including an untrusted/self-signed chain, an expired/not-yet-valid certificate, or ERR_TLS_CERT_ALTNAME_INVALID. A concrete certificate cause takes precedence over generic socket/proxy wrapper codes. Captured fetch causes apply this policy after adapter error flattening as well as during direct classification. Unknown and non-certificate TLS/protocol errors retain existing recovery behavior.

The transcript keeps the stable error code, transport errno and raw details, but uses localized certificate guidance instead of the generic connectivity summary. It asks the user to check the certificate, clock, and trusted roots used by security software/proxies, then restart after changing trust. It does not claim that interception is the only possible cause or offer a TLS bypass. Manual Continue remains available after the cause is corrected.

Local-first · Model-agnostic · Plugin-powered. AIUO.NET