Error Reference
Every public reject/error id the library can raise, with what raises it and what to do about it.
Error ids are API. Every id below is part of the public API and safe to branch on. The
stability promise covers them: while the library is on 0.x, an id can
only change in a 0.MINOR.0 release, with a changelog entry. Program
against the id, never the message string: messages are for humans and may be reworded. An error
arrives as a NiceError. Branch on it with err.hasId("..."), or narrow to a domain first with
err_domain.isExact(err):
// doc-check: skip — illustrative branching against ids documented belowif (err_realm.isExact(e) && e.hasId("insufficient_security")) { // handle the specific case}Realm rejections arrive on onMutationRejected
(their .error is a NiceError). Connection failures reject connect()/dispatch. Validation
failures come back as a typed action result. The tables group ids by the package that raises them.
Each section heading shows the domain name, which is the string you see in logs and devtools.
For the realm and connection domains the export you import is shorter: err_realm from
@nice-code/realm and err_wire_connect from @nice-code/wire. The action domains are exported
under the same name as their domain.
Realm — err_nice_realm
Section titled “Realm — err_nice_realm”Raised by the realm engine on attach and on each write/intent. A rejection here either rolled back
the optimistic effect (reported via onMutationRejected) or refused the attach (whenLive()
rejects, realm.attachError is set).
| Id | What emits it | What to do |
|---|---|---|
schema_mismatch | Client and server built from different realm definitions (hash mismatch). | Redeploy so both ends share one definition. This is a build-skew bug and is never retried. |
realm_unknown | The server hosts no realm with that id. | Fix the realm id, or serve that realm on the DO. |
unauthorized_avatar_type | The avatar’s type isn’t in the realm’s permitted set. | Connect as a permitted avatar type. |
avatar_unresolved | The connection’s identity mapped to no avatar (resolveAvatar returned nothing). | Ensure the connection carries an authenticated coordinate, or set avatarType. |
insufficient_security | The connection’s negotiated wire level is below the realm’s minimum. | Raise the connection’s securityLevel (e.g. encrypted). Fail-closed by design. |
unauthorized_coordinate | A write targets a path this avatar’s rules forbid. | Expected on a rules violation. Reflect it in the UI; don’t retry blindly. |
value_invalid | A written value doesn’t match the schema. | Fix the value shape; this is a program error. |
limits_exceeded | An ingress limit was hit (frame bytes / patches / write rate). | The client coalescer usually splits automatically; a bare breach means back off. |
intent_unknown | An intent name the realm doesn’t define. | Fix the intent name (it’s part of the schema hash). |
version_gap_unrecoverable | The client’s version is behind the replayable window. | Automatic: the client full-rehydrates. No app action. |
write_before_hydrate | A write or intent was queued before the first hydrate, and the hydrate never came (the realm detached, or pendingExpiryMs passed). Nothing was sent. | Check realm.status and the connection. Retry after reconnecting if the write still matters. |
frame_too_stale | An optimistic frame expired before an authoritative answer — an unknown outcome. | Re-read state; do not blindly re-issue a non-idempotent intent (it may have committed). Detect with isUnknownOutcomeRejection. |
durable_backlog_overflow | Too many unconfirmed durability: "reassert" frames (a long outage). | Re-sync from your own source of truth after reconnecting. |
frame_invalid | A malformed/hostile realm frame (fuzz gate). | Not app-actionable; the codec dropped it. Investigate a buggy/hostile peer. |
write_target_missing | A well-formed write whose target’s parent no longer exists (a pruned entry / a transient region reset by a wake). | Re-read state; an assertOnAttach re-creates a pruned own-entry. Distinct from frame_invalid. |
Connection — err_nice_wire_connect
Section titled “Connection — err_nice_wire_connect”Raised while opening or maintaining a wire connection (createWireClient / connectChannel).
| Id | What emits it | What to do |
|---|---|---|
connect_exhausted | Every carrier in the preference chain failed. | Check the endpoints/network; the reconnection ladder retries a duplex link. |
connect_closed | connect() on a client already close()d — terminal. | Build a new client; a closed one never reconnects. |
dial_failed | One transport’s dial or handshake failed (a chain-item detail inside connect_exhausted). | Read endpoint + kind (see below), then reason. |
endpoint_answered_non_protocol | Something answered and it is not nice-wire — an HTTP error page, a proxy/WAF, a different server. Carries url, httpStatus, contentType, and a capped bodyPreview. | Fix the URL, or whatever sits in front of it. The preview usually names the culprit outright. |
endpoint_unreachable | Nothing answered: DNS, connection refused, TLS, offline, or a socket that errored before opening. Carries url + the platform’s reason. | Start the server, open the firewall, check the network. |
handshake_rejected | The peer is a nice-code backend and refused the handshake (dictionary/security mismatch, a custom gate). | Fix the identity or version, not the address. identity_pin_mismatch is the pinned-key flavour below. |
protocol_version_mismatch | A secure peer explicitly reported that the two builds have no compatible first-party protocol generation. Carries the local supported list and bounded remote/observed version details when supplied. | Upgrade or roll back one side, then retry explicitly. Carrier fallback and automatic redial park; this cannot silently downgrade to plain HTTP. |
peer_identity_mismatch | A secure endpoint advertised a different envId, or a different explicitly expected perId. Context contains expected/actual identities and a sanitized destination when available. | Fix the route or peer expectation, then retry explicitly. Carrier fallback and automatic redial park. |
conflicting_connect_options | Both old and new forms were passed together (carriers + transports, reconnect + keepLinkAlive, heartbeat + linkKeepalive, or both cache-key names). | Pass one form. Prefer the new names. |
dial_unavailable | The carrier has no valid endpoint right now (its request callback returned null). | A policy signal, not a failure. The reconnection loop parks until the next explicit connect()/dispatch. |
protocol_unsupported | A protocol frame sent on a connection whose peer never advertised it. | Register the protocol before the handshake (dial with the realm/devtools mux attached). |
protocol_on_exchange_only | A prefixed protocol registered on an exchange-only (request/reply) connection. | Thrown at bring-up, not connect. A protocol needs a duplex carrier (a realm can’t run over HTTP-exchange alone). |
protocol_security_blocked | A protocol frame on a non-authenticated connection that didn’t opt into allowPlain. | Raise the connection’s security level, or the protocol must opt in (devtools does). |
binding_version_mismatch | A rehydrated hibernation binding has an unknown schema version. | Automatic: the socket is treated as fresh and re-handshakes. |
identity_pin_mismatch | The peer rejected the handshake: this client’s verify key ≠ the one pinned for its identity (TOFU). | Permanent for this identity+key pair, so the redial loop parks. Recover under a new persistent id, or clear the peer-side pin. Branch with err.hasId("identity_pin_mismatch"). |
Actions — err_nice_action, err_nice_transport, err_nice_transport_ws
Section titled “Actions — err_nice_action, err_nice_transport, err_nice_transport_ws”Raised by the action runtime and its transport. Most are program errors in wiring or registration. The validation and reliability ids are the ones you branch on.
err_nice_action — runtime & registration: not_implemented, action_id_not_in_domain,
domain_already_exists_in_hierarchy, domain_name_collision, domain_no_handler, hydration_domain_mismatch,
hydration_action_state_mismatch, hydration_action_id_not_found, no_action_execution_handler,
wire_action_not_payload, wire_not_action_data, runtime_reset, runtime_disposed,
ambiguous_action_runtime, invalid_server_runtime_scope, conflicting_server_options,
duplicate_action_executor, peer_handler_already_bound, conflicting_connect_options,
no_client_runtimes_registered, connection_required. The ids most often handled at an application boundary are:
-
ambiguous_action_runtime: more than one global runtime can execute an untargeted action. Dispatch throughruntime.run(request)or passtargetLocalRuntimeexplicitly. -
runtime_disposed: code tried to target or mutate a terminal runtime. Construct a new runtime; disposal is idempotent but cannot be reversed. -
invalid_server_runtime_scope: aserve*host received a global runtime. Construct it with{ scope: "instance" }so host handlers stay outside global dispatch. -
conflicting_server_options: a modernserve*call received bothidentityandruntime, or neither. Pass exactly one. Preferidentity; useruntimefor a caller-owned instance runtime. -
duplicate_action_executor: two distinct local handlers claim the same domain/action route at the same specificity. Keep one exact executor. An action-specific route may still override a domain fallback. -
connection_required: achannelCaseshandler calledsetState,clearState, orpushBackafter arriving over HTTP, or after its duplex binding was dropped/replaced. Use a duplex endpoint for socket state and pushes, or guard the operation withctx.connection. -
action_input_validation_failedandaction_output_validation_failed: a schema check failed (HTTP 400 for input, 500 for output). Fix the payload. -
action_input_validation_promiseandaction_output_validation_promise: a validator returned a promise, which isn’t supported. Make it synchronous.
err_nice_transport — dispatch & reliable delivery: timeout, not_found, unsupported,
initialization_failed (see My client can’t connect), send_failed,
invalid_action_response, and the three reliable-delivery ids you branch on:
reliable_outbox_overflow: the peer isn’t acknowledging, so the send was rejected to bound memory. Reconnect and re-sync.reliable_delivery_abandoned: the send wasn’t acked within its deadline, and the stream skipped past it.reliable_stream_closed: the stream was closed while a send was unacked.
err_nice_transport_ws — WebSocket carrier: ws_disconnected, ws_create_failed, ws_error
(transport-level, usually seen through the connection’s reconnect handling).
My client can’t connect
Section titled “My client can’t connect”initialization_failed means every transport in the chain failed. It names the endpoint
it was dialing, and carries a kind so you can branch without matching messages:
// doc-check: skip — illustrative branching against ids documented above// Both are re-exported from @nice-code/action (0.87+), so an actions consumer needs no wire import.import { err_nice_transport, EWireConnectFailureKind } from "@nice-code/action";
if (err_nice_transport.isExact(e) && e.hasId("initialization_failed")) { const { endpoint, kind } = e.getContext("initialization_failed"); switch (kind) { case EWireConnectFailureKind.endpoint_answered_non_protocol: // Something is there, but it is not the backend. Wrong URL, or a proxy/WAF in the way. break; case EWireConnectFailureKind.endpoint_unreachable: // Nothing is there. Server down, wrong port, firewall, offline. break; case EWireConnectFailureKind.handshake_rejected: // It *is* the backend and it said no — identity/dictionary/security, not the address. break; case EWireConnectFailureKind.protocol_version_mismatch: // The secure peers have no common protocol generation. Upgrade or roll back one side. break; }}kind | What it means | First thing to check |
|---|---|---|
endpoint_answered_non_protocol | An endpoint answered with something that isn’t a nice-wire frame. | The URL, then anything between you and it (proxy, WAF, tunnel). The error’s bodyPreview usually identifies it. |
endpoint_unreachable | Nothing answered at all. | Is the server running, on that port, reachable from this device? |
handshake_rejected | The backend answered and refused. | Client/server dictionaryVersion and security level; for a pinned identity see identity_pin_mismatch. |
protocol_version_mismatch | The backend explicitly reported no common secure-wire generation. | Compare each build’s WIRE_SUPPORTED_PROTOCOL_VERSIONS; deploy a tested compatible pair. |
unknown | The chain failed for something not yet classified. | Read cause and the origin error. |
The same kind values apply on both carriers. An HTTP exchange and a WebSocket dial classify
their failures identically, so your branch means the same thing everywhere.
Holding a dial error from somewhere other than an action call? classifyConnectFailure(error)
returns the same EWireConnectFailureKind for any error (unknown when it can’t tell), and
endpointFromConnectFailure(error) pulls out the dialed URL. The classifier is re-exported from
@nice-code/action; endpointFromConnectFailure lives in @nice-code/wire only. From an action
failure, read endpoint off the initialization_failed context instead, as above. See
Error Handling.
Validation — err_validation (@nice-code/common-errors)
Section titled “Validation — err_validation (@nice-code/common-errors)”| Id | What emits it | What to do |
|---|---|---|
standard_schema | A Standard-Schema validation failed (the /hono adapter, action input/output). | HTTP 400. The message carries the field issues; fix the payload. |
Branching helpers
Section titled “Branching helpers”err.hasId("id")— the primary check; true when the error carries that id anywhere in its domain chain.err_domain.isExact(err)— narrow to a domain (err_realm,err_wire_connect, …) before reading its ids, re-exported from the owning package (err_realm+isUnknownOutcomeRejectionfrom @nice-code/realm).isUnknownOutcomeRejection(rejection)— the realm helper for the one case that is genuinely ambiguous (frame_too_stale): treat it as “outcome unknown, re-read”, never as a definite reject.