From bf8e9452a8f5c0cd0ad932976992e3538f6a2865 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:42:58 -0700 Subject: [PATCH] Fix MCP Durable Object construction crash from Sentry prototype instrumentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `instrumentPrototypeMethods: true` makes Sentry walk the DO prototype and read every property (including accessors) to find methods to wrap. That invokes the `sessionId` getter with `this` bound to the prototype — where ctx is undefined — throwing "Cannot read properties of undefined (reading 'id')" and aborting construction on every session create and cold restore. Drop the flag (its purpose was to surface DO errors in Sentry) and capture init errors explicitly via the captureCause seam instead. --- apps/cloud/src/server.ts | 11 ++++++----- .../cloudflare/src/mcp/session-durable-object.ts | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index e941de78d..0ac72c93f 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -22,11 +22,12 @@ const sentryOptions = (env: Env) => ({ enableLogs: true, sendDefaultPii: true, skipOpenTelemetrySetup: true, - // Our DO methods (init/handleRequest/alarm) live on the prototype, not on - // the instance. Sentry's default DO auto-wrap only visits own properties, - // which misses prototype methods — so errors thrown inside init() never - // reach Sentry. This flag opts into prototype-method instrumentation. - instrumentPrototypeMethods: true, + // NOTE: do NOT enable `instrumentPrototypeMethods`. It walks the DO prototype + // and reads every property — including accessors — to find methods to wrap, + // which invokes the `sessionId` getter with `this` bound to the prototype + // (where `ctx` is undefined) and throws during construction, 500ing every + // session create / cold restore. The DO captures its own errors via the + // `captureCause` seam (→ Sentry) instead. }); // --------------------------------------------------------------------------- diff --git a/packages/hosts/cloudflare/src/mcp/session-durable-object.ts b/packages/hosts/cloudflare/src/mcp/session-durable-object.ts index 943368ac6..250911f31 100644 --- a/packages/hosts/cloudflare/src/mcp/session-durable-object.ts +++ b/packages/hosts/cloudflare/src/mcp/session-durable-object.ts @@ -628,6 +628,10 @@ export abstract class McpSessionDOBase< Effect.tapCause((cause) => Effect.gen(function* () { console.error("[mcp-session] init failed:", Cause.pretty(cause)); + // Report to the host's error sink (cloud → Sentry). init() runs on + // the prototype, which Sentry's auto-wrap doesn't cover, so capture + // here explicitly rather than relying on prototype instrumentation. + self.captureCause(cause); // Annotate `McpSessionDO.init` (the active span here — `doInit` opens // none of its own) so the surviving, flushed span names the frame. yield* self.recordCauseOnSpan(cause);