Skip to content

fix(rivetkit): expose engine ray IDs on errors#5456

Open
abcxff wants to merge 1 commit into
mainfrom
fix/riveterror-ray-id
Open

fix(rivetkit): expose engine ray IDs on errors#5456
abcxff wants to merge 1 commit into
mainfrom
fix/riveterror-ray-id

Conversation

@abcxff

@abcxff abcxff commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • expose x-rivet-ray-id as the top-level RivetError.rayId field without changing application error metadata
  • preserve Ray IDs through HTTP decoding, retries, remapping, scheduling errors, WebSocket close errors, reload failures, and TS/NAPI bridge round trips
  • include Ray IDs in structured HTTP error logs and document the expanded NAPI error payload

No Trace ID support or engine response contract changes are included.

Testing

  • pnpm exec vitest run tests/rivet-error.test.ts
  • pnpm run check-types in rivetkit-typescript/packages/rivetkit
  • Biome check on touched TypeScript files
  • git diff --check

Environment limitations

  • The driver matrix could not start in the orb because @rivetkit/engine-cli-linux-x64-musl is unavailable.
  • The NAPI Rust test was added but could not run because Cargo/rustfmt are not installed in the orb.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review

Overall this is a clean, well-scoped change: rayId is threaded consistently through RivetError, the client fetch/WebSocket/retry paths, deconstructError, and the NAPI bridge round trip, with solid new unit test coverage (round-trip encode/decode, metadata isolation, HTTP header attachment, Rust bridge payload round trip). Style matches repo conventions (structured tracing fields, no _ => fallthrough issues, hard tabs in Rust, etc.).

Correctness gap: the actor's own HTTP router never emits rayId

common/router.ts::handleRouteError is the function that turns an in-actor error into the HTTP response sent back to a caller. It calls the newly-updated deconstructError(...), but only destructures { statusCode, group, code, message, metadata, actor }rayId is dropped:

const { statusCode, group, code, message, metadata, actor } =
    deconstructError(error, exposeInternalError);
...
const errorData = { group, code, message, metadata, actor };
...
return c.body(output as any, { status: statusCode, headers });

No x-rivet-ray-id header is set on the outgoing response, and HttpResponseErrorSchema has no rayId field either. Meanwhile the client-side code this PR adds (client/utils.ts, client/actor-handle.ts) explicitly reads x-rivet-ray-id from response headers to populate RivetError.rayId. So for a RivetError that originates inside an actor (e.g. an internal engine/KV/SQL call that failed and carried an engine ray ID, now preserved on the error per this PR), that ray ID is captured all the way through deconstructError, then silently discarded at the last step before it reaches the wire. This undercuts the PR's own stated goal of preserving ray IDs "through HTTP decoding ... scheduling errors ... reload failures" — it holds for gateway/engine-originated errors decoded on the client, but not for errors an actor raises and returns through its own router.

Related: the default sanitized-error branch also drops rayId

In common/utils.ts::deconstructError, the final else branch (hit whenever exposeInternalError is false and the error isn't public — the default in production, since RIVET_EXPOSE_ERRORS is unset unless explicitly enabled) builds a generic opaque INTERNAL_ERROR_DESCRIPTION response. It preserves actor when available, but never assigns rayId:

} else {
    statusCode = 500;
    public_ = false;
    group = "rivetkit";
    code = errors.INTERNAL_ERROR_CODE;
    message = errors.INTERNAL_ERROR_DESCRIPTION;
    if (errors.ActorError.isActorError(error)) {
        actor = error.actor;
    }
    metadata = { ... };
}

Since a ray ID is meant to be a safe, non-sensitive correlation handle (per the new doc comment: "used to correlate this error with engine logs"), this is exactly the branch where surfacing it would be most valuable — an opaque 500 with no other diagnostic info. Worth adding rayId = error.rayId alongside the actor assignment here (and then wiring it out through handleRouteError above) to fully close the loop this PR is opening.

Minor / non-blocking

  • The rivetkit-napi/src/actor_context.rs verify_inspector_auth_js change (ray_id: None) is correct as-is — that's a locally-generated 401, no upstream ray ID exists.
  • Test coverage for the client-header path and the Rust bridge round trip is solid; per the PR description, the NAPI Rust test and the driver matrix couldn't actually be run in the author's environment, so it'd be good to confirm CI runs cargo test -p rivetkit-napi and the driver suite before merge.

No security or performance concerns — this is purely metadata plumbing over already-established boundaries, and no unbounded/sensitive data is added to logs or metrics.

@railway-app

railway-app Bot commented Jul 21, 2026

Copy link
Copy Markdown

🚅 Deployed to the rivet-pr-5456 environment in rivet-frontend

Service Status Web Updated (UTC)
frontend-inspector 😴 Sleeping (View Logs) Web Jul 21, 2026 at 2:40 am
frontend-cloud 😴 Sleeping (View Logs) Web Jul 21, 2026 at 2:38 am
website 😴 Sleeping (View Logs) Web Jul 21, 2026 at 2:38 am
kitchen-sink 😴 Sleeping (View Logs) Web Jul 21, 2026 at 2:37 am
ladle ✅ Success (View Logs) Web Jul 21, 2026 at 2:32 am
mcp-hub ✅ Success (View Logs) Web Jul 21, 2026 at 2:30 am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants