Skip to content

@sentry/cloudflare: Durable Object RPC methods drop metrics, logs and errors unless the caller propagates a trace #24443

Description

@msnelling

Environment

  • @sentry/cloudflare: 10.74.0 (same logic on develop / 11.0.0-beta.2)
  • DO wrapped with instrumentDurableObjectWithSentry, enableRpcTracePropagation: true (the only
    supported way to instrument RPC methods in v11, now that instrumentPrototypeMethods is removed)

Description

createRpcPrototypeWrapper only calls wrapMethodWithSentry when the last argument carries
__sentry_rpc_meta__. Otherwise the original method runs with no client bound. Then:

  • Sentry.metrics.count/gauge/distribution return early (no client);
  • Sentry.captureException and Sentry.logger.* are dropped;
  • no rpc span is recorded, and a thrown error is not captured.

Callers send the metadata only if they are Sentry-instrumented, have an active trace, and got the stub
through get/getByName on the instrumented env (see #24442). Common cases that lose everything:

  • calls from uninstrumented workers or mixed deployments;
  • calls from code running outside the request's async context;
  • any call through jurisdiction().

WorkerEntrypoint already covers this case: instrumentWorkerEntrypoint uses a capture-only wrapper
when the metadata is missing. Durable Objects have no equivalent. fetch, alarm and webSocket* are
always wrapped, so only RPC is affected. That makes the gap easy to miss: our production data had
14 days of DO alarm spans and zero DO rpc spans.

Reproduction

class MyDO extends DurableObject {
  async ping() {
    Sentry.metrics.count('my_do.ping', 1);
    throw new Error('boom');
  }
}
export const MyDOInstrumented = Sentry.instrumentDurableObjectWithSentry(
  env => ({ dsn: env.SENTRY_DSN, tracesSampleRate: 1, enableRpcTracePropagation: true }),
  MyDO,
);
// From a plain (uninstrumented) worker:
await env.MY_DO.get(env.MY_DO.idFromName('x')).ping();

Neither the metric nor the error reaches Sentry.

Expected

External RPC calls without metadata still run inside a client and isolation scope with flush, as
WorkerEntrypoint does. Internal this.method() calls stay unwrapped.

Proposed fix

A PR with this change will follow.

Activity

  1. linear-code commented on Sep 16, 2026

    @linear-code
  2. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Sep 16, 2026
  3. JPeer264 commented on Sep 16, 2026

    @JPeer264
    Member

    Interesting, this must be a regression as this sounds like a basic use case. I saw that you also opened a PR here, but it seems to be combining another ticket - would you be so kind to split these two up? That would make reviewing easier and could potentially block one or the other so we can get them in faster individually. Unless they only can go together, but AFAICT this isn't the case

  4. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Sep 16, 2026
  5. msnelling commented on Sep 16, 2026

    @msnelling
    ContributorAuthor

    Thanks! No, they don't need to go together, so I've split them:

    Either can be merged on its own. Without #24450, calls through jurisdiction() still carry no trace metadata, but with #24447 they are captured in a new trace rather than dropped. So each PR fixes its own problem independently.

  6. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Sep 16, 2026
  7. JPeer264 commented on Sep 17, 2026

    @JPeer264
    Member

    Thanks a lot for splitting them up, that's amazing. I'll take a look at them.

  8. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Sep 17, 2026
  9. added 2 commits that reference this issue on Sep 17, 2026
    99d40f8
    c8f750f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions