Skip to content

Commit 083209a

Browse files
authored
Answer not found for requests that land mid-destroy on an MCP session (#2391)
destroy() wipes the session's storage and the SDK's tables before it aborts the isolate. A request landing in between passed the condemned-marker check, since the marker was wiped too, and then trusted the in-memory session meta. It restored the runtime against the dropped tables and failed with "no such table: cf_agents_mcp_servers" as an unhandled 500. Once destroy starts, the cached meta no longer vouches for the session.
1 parent dc3eac8 commit 083209a

2 files changed

Lines changed: 18 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@executor-js/cloud": patch
3+
---
4+
5+
An MCP request that reaches a session while the session is being torn down now gets the "Session not found" protocol error instead of a 500.

‎packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,15 @@ export abstract class McpAgentSessionDOBase<
356356
* second time.
357357
*/
358358
private disposingRuntime: Promise<void> | null = null;
359+
/**
360+
* Set once `destroy()` begins. The SDK's teardown drops its tables and wipes
361+
* storage (the condemned marker included) before it aborts the isolate, and
362+
* requests still land on this instance in between. From this point the
363+
* in-memory session meta no longer describes stored state, so it must not
364+
* vouch for the session: a request that trusted it would restore the runtime
365+
* against dropped tables and fail as an unhandled error.
366+
*/
367+
private destroyStarted = false;
359368
private onStartPromise: Promise<void> | null = null;
360369
private lastActivityMs = 0;
361370
private resolvedSessionName: string | undefined = undefined;
@@ -650,6 +659,9 @@ export abstract class McpAgentSessionDOBase<
650659

651660
private loadSessionMeta(): Effect.Effect<SessionMeta | null> {
652661
return Effect.promise(async () => {
662+
// See `destroyStarted`: once teardown begins, the cached meta outlives
663+
// the storage it mirrors.
664+
if (this.destroyStarted) return null;
653665
if (this.sessionMeta) return this.sessionMeta;
654666
const stored = await this.ctx.storage.get<SessionMeta>(SESSION_META_KEY);
655667
// Backfill `resource` for sessions persisted before scoped toolkits added
@@ -1784,6 +1796,7 @@ export abstract class McpAgentSessionDOBase<
17841796
}
17851797

17861798
override async destroy(): Promise<void> {
1799+
this.destroyStarted = true;
17871800
await this.cleanup();
17881801
await super.destroy();
17891802
}

0 commit comments

Comments
 (0)