Skip to content

feat(rivetkit): pass trace context to js and logs - #5723

Open
eersnington wants to merge 1 commit into
stack/feat-rivetkit-trace-sqlite-operations-qpnpswllfrom
stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt
Open

eersnington wants to merge 1 commit into
stack/feat-rivetkit-trace-sqlite-operations-qpnpswllfrom
stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt

Conversation

@eersnington

@eersnington eersnington commented Sep 15, 2026

Copy link
Copy Markdown
Member
  • Making the current action's trace available in JS so application spans can sit under it
  • Adding actor, ray and trace IDs to actor logs, and passing trace context through actor clients
  • Keeping the action span open until its waitUntil work finishes

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 1 medium-severity finding

Reviewed commit 6a1b670.

Comment on lines +580 to +584
ctx: ActorContextHandle,
): ActorInvocationTraceContext | undefined {
return this.#actorInvocationTraceContext(asNativeActorContext(ctx));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · Resolve trace context through the active invocation

actorInvocationTraceContext reads the context captured when the client was created, rather than the invocation in #invocationContext. A client can be retained in actor state/vars (or captured by run) and used during a later action; its captured context has already finished, so this returns undefined and the actor-to-actor request drops both trace headers and the ray ID.

This runtime already resolves SQLite operations through #actorContextForOperation for this exact retained-handle case. Use that resolver before reading invocationTraceContext() so a same-instance active action supplies its current context.

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Code Review

Re-reviewed against the current diff (updated 2026-09-22, HEAD 561a425c). This supersedes the previous pass. Findings below were re-verified against the current code; several carry over unchanged, others are new.

Findings

  1. rivetkit-rust/packages/rivetkit-core/src/telemetry.rs:336-345 (finish_with) - Unconditionally logs "reply sent" whenever pending_work > 0, even though this helper is shared by both finish() (line 325, the real success/error path) and finish_dropped() (line 329, the abandoned-reply path used when an ActionInvocationSpan is dropped without a reply, e.g. actor force-stopped mid-flight). finish_dropped() correctly marks otel.status_code = ERROR / error.type = actor.dropped_reply, but if a wait_until task is still holding pending_work > 0 at that point, the exported trace/log also claims "reply sent", which is actively misleading during an abort investigation since no reply was ever sent on that path.

  2. rivetkit-typescript/packages/rivetkit/src/engine-client/actor-http-client.ts:61-72 (buildGuardHeaders) - The traceparent/tracestate loop explicitly does headers.delete(name) before conditionally re-setting from the real request, so a value statically configured in runConfig.headers can never leak through (per the comment: "Configured trace context is never sent: it would pin every call to one span"). The x-rivet-ray-id block directly below has no equivalent headers.delete(HEADER_RIVET_RAY_ID); it only does if (requestRayId !== null) headers.set(...). If runConfig.headers ever contains a static x-rivet-ray-id and the incoming request carries no ray ID of its own, that stale value survives untouched into the outgoing gateway request, pinning every such call to one ray ID, the exact failure mode the header above was written to avoid.

  3. rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts - Trace/ray-id propagation is wired into exactly one outgoing path: #sendActionNow (~line 331), via #currentActorInvocation and actorInvocationTraceHeaders. #sendQueueMessage (line 147) and #fetchWithResolvedActor (line 682, backing the raw .fetch()/.websocket() client surface) never read #currentActorInvocation and never set any trace/ray headers. An actor calling another actor via c.client.b.getOrCreate().queue.send(...) or .fetch(...) mid-invocation silently starts a brand-new, uncorrelated trace on the callee, while the same call via .someAction() propagates correctly. This is inconsistent behavior depending on call shape, for a PR whose stated goal is passing trace context through actor clients generally.

  4. rivetkit-typescript/packages/rivetkit/src/registry/wasm-runtime.ts:546-550 - actorInvocationTraceContext(_ctx) is a hardcoded return undefined, with no attempt to forward to rivetkit-core. rivetkit-typescript/CLAUDE.md states: "Wasm bindings for NAPI-supported runtime APIs should forward to rivetkit-core; avoid placeholder returns that break runtime parity." As written, every wasm-hosted actor gets zero trace/ray-id correlation for c.log and actor-to-actor clients, silently diverging from the NAPI runtime for this new capability.

  5. rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts:550-557 - The new doc comment on runWithActorInvocationContext says "Only actions, raw requests, and queue dispatch run this way." In registry/native.ts, runtime.runWithActorInvocationContext(...) is only ever called from the actions: handler map (line 5362); the onRequest (line 5053) and onQueueSend (line 5370) handlers never call it. A reader relying on the comment would wrongly assume raw HTTP requests and queue dispatch get an active invocation span or c.log correlation. They currently do not, which also underlines finding [SVC-2504] Fix 5 GB upload limit for local development from Cloudflare #3 above.

Minor / lower confidence

  • rivetkit-rust/packages/rivetkit-core/src/actor/context.rs:763-789 - the native and wasm wait_until variants duplicate the identical hold_open + drop(invocation) wrapper verbatim across the two #[cfg] branches (only the Send bound differs, matching the existing pattern for register_task/spawn_work in this file). Low-impact, but a future change to hold-open semantics needs to be kept in sync by hand in both places.
  • rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts:331 - #currentActorInvocation?.() is invoked inside #sendActionNows retry loop, so a call that retries several times on overload re-fetches trace context from the NAPI side on every attempt even though it cannot change mid-invocation. Likely negligible relative to actor I/O latency, but easy to hoist above the loop if it shows up in profiling.

Test coverage

Notes

  • No other CLAUDE.md convention violations spotted (comment style, structured logging, parking_lot::Mutex usage in Drop/sync paths, exhaustive enum matches all match repo conventions in this diff).

🤖 Generated with Claude Code

@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 6a1b670 to 483d6f1 Compare September 16, 2026 01:18

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 1 medium-severity finding

Reviewed commit 483d6f1.

Comment on lines +580 to +584
ctx: ActorContextHandle,
): ActorInvocationTraceContext | undefined {
return this.#actorInvocationTraceContext(asNativeActorContext(ctx));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · Resolve trace context through the active invocation

actorInvocationTraceContext reads the context captured when the client was created, rather than the invocation in #invocationContext. A client can be retained in actor state/vars (or captured by run) and used during a later action; its captured context has already finished, so this returns undefined and the actor-to-actor request drops both trace headers and the ray ID.

This runtime already resolves SQLite operations through #actorContextForOperation for this exact retained-handle case. Use that resolver before reading invocationTraceContext() so a same-instance active action supplies its current context.

@eersnington

Copy link
Copy Markdown
Member Author

No test verifies ctx.log is enriched with rayId/traceId/spanId

followed up in #5732, including overlapping invocations

No test verifies ActorHandleRaw/sendHttpRequestToGateway actually forwards traceparent/tracestate/x-rivet-ray-id on an actor-to-actor call

followed up in #5726 and #5732

No unit tests for the new pure helpers in otel-context.ts

my clank in christ, you really love this don't you

covering hold_open/finish/guard-drop ordering

the waitUntil driver test checks the exported span's lifetime

This is safe today because every ActorContextHandleAdapter/conn-context instance is freshly constructed per dispatch

yeah ig?

@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 483d6f1 to 08b79f2 Compare September 16, 2026 18:16

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 1 medium-severity finding

Reviewed commit 08b79f2.

Comment on lines +580 to +584
ctx: ActorContextHandle,
): ActorInvocationTraceContext | undefined {
return this.#actorInvocationTraceContext(asNativeActorContext(ctx));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · Resolve trace context through the active invocation

actorInvocationTraceContext reads the context captured when the client was created, rather than the invocation in #invocationContext. A client can be retained in actor state/vars (or captured by run) and used during a later action; its captured context has already finished, so this returns undefined and the actor-to-actor request drops both trace headers and the ray ID.

This runtime already resolves SQLite operations through #actorContextForOperation for this exact retained-handle case. Use that resolver before reading invocationTraceContext() so a same-instance active action supplies its current context.

@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 08b79f2 to 58dfd19 Compare September 16, 2026 18:24

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found

Reviewed commit 58dfd19.

@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 58dfd19 to 9df89c4 Compare September 16, 2026 18:34
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 9df89c4 to 831cfcf Compare September 16, 2026 18:42
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 831cfcf to 16670ba Compare September 16, 2026 19:13
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 16670ba to 4da8a6c Compare September 16, 2026 20:28
@eersnington
eersnington added this pull request to stack #5746 September 17, 2026 08:36
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from 4da8a6c to f19cca8 Compare September 17, 2026 15:14
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from f19cca8 to dc29956 Compare September 18, 2026 22:40
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from dc29956 to c820481 Compare September 19, 2026 01:01
@eersnington
eersnington force-pushed the stack/feat-rivetkit-pass-trace-context-to-js-and-logs-lpqzmttt branch from c820481 to 561a425 Compare September 22, 2026 15:28

This branch has not been deployed

No deployments
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.

1 participant