Skip to content

feat(cloudflare): Add trace propagation for RPC method calls#20343

Draft
JPeer264 wants to merge 1 commit intodevelopfrom
jp/real-rpc
Draft

feat(cloudflare): Add trace propagation for RPC method calls#20343
JPeer264 wants to merge 1 commit intodevelopfrom
jp/real-rpc

Conversation

@JPeer264
Copy link
Copy Markdown
Member

closes #19327
closes JS-1715

closes #16898
closes JS-680

closes #16760
closes JS-622

Summary

Most of the additions are tests, the main implementation is rather small

Adds trace propagation for Cloudflare Workers RPC method calls to Durable Objects.

This is admittedly a bit of a hack: Cap'n Proto (which powers Cloudflare RPC) has no native support for headers or metadata. To work around this, we append our trace data (sentry-trace + baggage) as a trailing argument object { __sentry: { trace, baggage } } to every RPC call. On the receiving DO side, we strip this argument before the user's method is invoked, so it's completely transparent.

Caveat: If the Durable Object is not instrumented with Sentry, the trailing __sentry argument will remain in the args array and be passed to the user's method. I would count this as ok since:

  • Users opting into RPC instrumentation are expected to instrument both sides
  • The extra argument is easy to ignore in most cases, unless users use ...args to retrieve all arguments

Otherwise, trace propagation should be seamless across Worker → DO and Worker → Worker → DO call chains.

New option: enableRpcTracePropagation

instrumentPrototypeMethods has been deprecated in favor of enableRpcTracePropagation

Replaces the deprecated instrumentPrototypeMethods option with a clearer name that describes what it actually does. This option must be enabled on both the caller (Worker) and receiver (Durable Object) sides for trace propagation to work.

It is also worth to mention that the implementation of "instrumenting prototype methods" has changed to a Proxy.

// Worker side
export default Sentry.withSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    enableRpcTracePropagation: true,
  }),
  handler,
);

// Durable Object side
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    enableRpcTracePropagation: true,
  }),
  MyDurableObjectBase,
);

How it works

As mentioned above a Sentry trace object is appended on each call

const id = env.MY_DURABLE_OBJECT.idFromName('test');
const stub = env.MY_DURABLE_OBJECT.get(id);

// User's RPC call
const result = await stub.sayHello('World');

// What is actually sent (transparent to user)
const result = await stub.sayHello('World', { __sentry: { trace, baggage } });

@JPeer264 JPeer264 self-assigned this Apr 16, 2026
Comment on lines +37 to +41
// instrumentPrototypeMethods can be boolean or string[], convert to boolean
return (
instrumentPrototypeMethods === true ||
(Array.isArray(instrumentPrototypeMethods) && instrumentPrototypeMethods.length > 0)
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The instrumentPrototypeMethods option no longer supports selective instrumentation. Providing an array of method names incorrectly instruments all methods, not just the specified ones.
Severity: MEDIUM

Suggested Fix

Update the proxy's get trap in durableobject.ts to check if instrumentPrototypeMethods is an array. If it is, only wrap methods whose names are included in that array. The boolean conversion in rpcOptions.ts should be removed, and the array of method names should be passed through to the instrumentation logic.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/cloudflare/src/utils/rpcOptions.ts#L37-L41

Potential issue: The implementation for `instrumentPrototypeMethods` has a regression.
When an array of method names is provided to selectively instrument specific RPC
methods, the code at `rpcOptions.ts` incorrectly converts the array to a boolean,
discarding the method names. Consequently, the proxy logic in `durableobject.ts`
instruments all prototype methods instead of only the ones specified in the array. This
is a silent behavioral change that violates the documented API contract, which still
claims selective instrumentation is supported. Users expecting only certain methods to
be instrumented will have all of them instrumented instead.

Also affects:

  • packages/cloudflare/src/durableobject.ts:140~174

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7e5c662. Configure here.

return (
instrumentPrototypeMethods === true ||
(Array.isArray(instrumentPrototypeMethods) && instrumentPrototypeMethods.length > 0)
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deprecated array option silently instruments all methods

Medium Severity

The deprecated instrumentPrototypeMethods option accepted string[] for selective method instrumentation. getEffectiveRpcPropagation collapses any non-empty array to true, and the new Proxy in durableobject.ts wraps ALL prototype methods unconditionally. Users relying on instrumentPrototypeMethods: ['specificMethod'] now silently get every method instrumented. The documentation in client.ts still claims "only the specified method names will be instrumented."

Additional Locations (2)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 7e5c662. Configure here.


while (current && current !== Object.prototype) {
Object.getOwnPropertyNames(current).forEach(name => {
if (name !== 'constructor' && typeof (current as Record<string, unknown>)[name] === 'function') {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Object.prototype methods wrapped as RPC methods

Low Severity

The RPC Proxy's get trap only excludes methods in BUILT_IN_DO_METHODS. Inherited Object.prototype methods like toString, valueOf, and hasOwnProperty are not own properties and not in the exclusion set, so they pass all filters and get wrapped with wrapMethodWithSentry as RPC methods. Accessing them (e.g., via string coercion or logging) would create unwanted rpc spans and initialize a Sentry client.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7e5c662. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.78 kB - -
@sentry/browser - with treeshaking flags 24.27 kB - -
@sentry/browser (incl. Tracing) 43.61 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 45.32 kB - -
@sentry/browser (incl. Tracing, Profiling) 48.51 kB - -
@sentry/browser (incl. Tracing, Replay) 82.74 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 72.25 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 87.43 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 99.67 kB - -
@sentry/browser (incl. Feedback) 42.59 kB - -
@sentry/browser (incl. sendFeedback) 30.45 kB - -
@sentry/browser (incl. FeedbackAsync) 35.45 kB - -
@sentry/browser (incl. Metrics) 27.07 kB - -
@sentry/browser (incl. Logs) 27.2 kB - -
@sentry/browser (incl. Metrics & Logs) 27.89 kB - -
@sentry/react 27.53 kB - -
@sentry/react (incl. Tracing) 45.88 kB - -
@sentry/vue 30.61 kB - -
@sentry/vue (incl. Tracing) 45.45 kB - -
@sentry/svelte 25.8 kB - -
CDN Bundle 28.46 kB - -
CDN Bundle (incl. Tracing) 44.69 kB - -
CDN Bundle (incl. Logs, Metrics) 29.83 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 45.78 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.73 kB - -
CDN Bundle (incl. Tracing, Replay) 81.65 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 82.73 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 87.17 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 88.23 kB - -
CDN Bundle - uncompressed 83.12 kB - -
CDN Bundle (incl. Tracing) - uncompressed 133.64 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 87.27 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 137.05 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 210.63 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 250.87 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 254.27 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 263.78 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 267.17 kB - -
@sentry/nextjs (client) 48.42 kB - -
@sentry/sveltekit (client) 44.06 kB - -
@sentry/node-core 57.94 kB +0.02% +6 B 🔺
@sentry/node 174.78 kB +0.01% +8 B 🔺
@sentry/node - without tracing 97.89 kB +0.03% +20 B 🔺
@sentry/aws-serverless 115.12 kB +0.01% +7 B 🔺

View base workflow run

@JPeer264
Copy link
Copy Markdown
Member Author

Moved to draft, as tests are failing and I have to change 1-2 things that could reduce the amount lines added

JPeer264 added a commit that referenced this pull request Apr 16, 2026
…pagation (#20345)

follow up to #19991

It is better to release it first with an option to be enabled, that
would then also be in line with #20343, otherwise `.fetch()` RPC calls
would work without any option and the actual Cap'n'Proto RPC calls
wouldn't work without. That would be an odd experience.

### New option: `enableRpcTracePropagation`

> `instrumentPrototypeMethods` has been deprecated in favor of
`enableRpcTracePropagation`

Replaces the deprecated `instrumentPrototypeMethods` option with a
clearer name that describes what it actually does. This option must be
enabled on **both** the caller (Worker) and receiver (Durable Object)
sides for trace propagation to work.

It is also worth to mention that the implementation of "instrumenting
prototype methods" has changed to a Proxy.

```ts
// Worker side
export default Sentry.withSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    enableRpcTracePropagation: true,
  }),
  handler,
);

// Durable Object side
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    enableRpcTracePropagation: true,
  }),
  MyDurableObjectBase,
);
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant