Version
@sentry/cloudflare 10.75.1, with enableRpcTracePropagation: true
Summary
Since #24512, the RPC prototype wrapper instruments any prototype method called while the isolation scope is the default one, in a new trace. The wrapper is installed after construction, but a blockConcurrencyWhile callback started in the constructor runs after that, with no isolation scope. So a method the object calls on itself during initialisation is treated as an untraced RPC entry. If it throws, the error is captured as handled: false with mechanism auto.faas.cloudflare.durable_object, even when the object catches it.
Repro
class Store extends DurableObject {
failure?: Error;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
void ctx.blockConcurrencyWhile(async () => {
try { this.init(); } catch (e) { this.failure = e as Error; }
});
}
init(): void { throw new Error('init failed'); }
ping(): string { return this.failure ? 'degraded' : 'ok'; }
}
export const Instrumented = Sentry.instrumentDurableObjectWithSentry(
(env) => ({ dsn: env.SENTRY_DSN, enableRpcTracePropagation: true }),
Store,
);
Call ping() on a stub.
Expected: no error event. The error is caught inside the object.
Actual: one error event for init failed, handled: false, in its own trace.
Observed in real workerd (@cloudflare/vitest-pool-workers). With a raised Error.stackTraceLimit, the capturing frame is the untraced startNewTrace branch in durableobject.ts, wrapping the method called from the constructor's blockConcurrencyWhile callback.
Notes
Declaring internal methods as #private avoids it, because they are no longer on the prototype, and that matches Cloudflare's RPC visibility guidance (https://developers.cloudflare.com/workers/runtime-apis/rpc/visibility/). It may be worth either documenting that, or running constructor-time blockConcurrencyWhile callbacks inside an isolation scope so they are not mistaken for RPC entries.
Version
@sentry/cloudflare10.75.1, withenableRpcTracePropagation: trueSummary
Since #24512, the RPC prototype wrapper instruments any prototype method called while the isolation scope is the default one, in a new trace. The wrapper is installed after construction, but a
blockConcurrencyWhilecallback started in the constructor runs after that, with no isolation scope. So a method the object calls on itself during initialisation is treated as an untraced RPC entry. If it throws, the error is captured ashandled: falsewith mechanismauto.faas.cloudflare.durable_object, even when the object catches it.Repro
Call
ping()on a stub.Expected: no error event. The error is caught inside the object.
Actual: one error event for
init failed,handled: false, in its own trace.Observed in real workerd (
@cloudflare/vitest-pool-workers). With a raisedError.stackTraceLimit, the capturing frame is the untracedstartNewTracebranch indurableobject.ts, wrapping the method called from the constructor'sblockConcurrencyWhilecallback.Notes
Declaring internal methods as
#privateavoids it, because they are no longer on the prototype, and that matches Cloudflare's RPC visibility guidance (https://developers.cloudflare.com/workers/runtime-apis/rpc/visibility/). It may be worth either documenting that, or running constructor-timeblockConcurrencyWhilecallbacks inside an isolation scope so they are not mistaken for RPC entries.