Skip to content

Error envelope

The Matrix protocol does not use a separate error envelope shape. Errors are returned as a regular response envelope (op: '$reply') whose payload includes ok: false plus an error string and optional structured fields. This keeps the wire shape uniform — any caller code that parses replies parses errors with the same path.

The shape

typescript
interface MxErrorPayload {
  ok: false;
  error: string;          // human-readable message
  code?: string;          // machine-readable code, e.g. 'NOT_FOUND'
  statusCode?: number;    // for gateway / HTTP-mapped errors
  details?: unknown;      // op-specific structured detail
  // … any op-specific fields
}

A complete error reply:

json
{
  "op": "$reply",
  "correlationId": "f12d-7af0",
  "payload": {
    "ok": false,
    "error": "gateway.http.resolve requires a non-empty pathname",
    "statusCode": 400
  }
}

Real examples from the running code

From projects/matrix-3/packages/system-gateway-http/src/index.ts lines 294–340:

typescript
async onGatewayHttpResolve(payload): Promise<...> {
  const normalized = normalizeRequestPath(payload.pathname);
  if (!normalized) {
    return { ok: false, error: 'gateway.http.resolve requires a non-empty pathname' };
  }
  // ...
  return {
    ok: resolution !== null,
    pathname: normalized,
    resolution,
    ...(resolution ? {} : { error: 'No Host runtime webapp route matched the request' }),
  };
}

async onGatewayHttpRequest(payload): Promise<...> {
  const method = (readString(payload.method) || 'GET').toUpperCase();
  if (method !== 'GET' && method !== 'HEAD') {
    return {
      ok: false,
      statusCode: 405,
      headers: { allow: 'GET, HEAD' },
      error: 'gateway.http.request only supports GET and HEAD',
    };
  }
  // ...
  return {
    ok: false,
    statusCode: 404,
    pathname: normalized,
    error: 'No Host runtime webapp route matched the request',
  };
}

From projects/matrix-3/packages/system-runtimes/src/RuntimeManagerActor.ts line 241:

typescript
return {
  ok: false,
  error: 'runtimes.reconcile is not available in daemonless Host mode; use Host Service package/runtime APIs.',
};

These show three patterns that appear consistently:

  1. Always set ok: false when the op cannot fulfill the request.
  2. Always set a human-readable error, written for an operator who sees it in a log.
  3. Add structured fields when the caller needs them: statusCode for gateway errors, mount/path identifiers when relevant.

Conventions

FieldWhen to include
okAlways. false is mandatory on errors.
errorAlways. Plain English message.
codeWhen the caller might pattern-match (e.g., NOT_FOUND, TIMEOUT, UNAUTHENTICATED). See Error codes.
statusCodeWhen the op maps to an HTTP-style status. Used by system.gateway.http and gateway adapters.
detailsOp-specific structured data the caller can use programmatically.

When the actor throws

If an op handler throws an unhandled exception, the framework catches it and replies with { ok: false, error: <message> }. Callers should not rely on this shape for error classification — actors should explicitly construct error responses, because the throw path does not include structured details.

When the transport fails

A request that times out (no reply within the timeout window) produces a transport-level rejection at the caller — there is no envelope on the wire to populate. The caller's promise rejects with a timeout error constructed locally. See Timeouts.

A request that fails at the NATS layer (publish error, connection closed) likewise rejects locally without a server-side envelope.

Errors vs $reply with empty payload

Returning { ok: true } with no other fields is a valid success response — it means "op completed; nothing to return". Returning { ok: false, error } is an error. Returning {} is ambiguous and should be avoided; new ops should always set ok explicitly.

Error code reference

Standardized error codes live in Error codes. The current set of codes seen in production is small and op-specific; the workstream goal is to converge on a shared catalog (NOT_FOUND, INVALID_ARGUMENT, UNAUTHENTICATED, PERMISSION_DENIED, UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED, ALREADY_EXISTS, RESOURCE_EXHAUSTED).

Status: target state, partial. Error code standardization is in progress. Many actors today return only error: <message> without a code. Document the codes your actor uses through static errorSpecs (declared on MatrixActor, surfaced via $introspect).

See also