Skip to content

MCP session cold init resilience - #1787

Merged
RhysSullivan merged 3 commits into
mainfrom
fix/mcp-session-cold-init
Aug 28, 2026
Merged

MCP session cold init resilience#1787
RhysSullivan merged 3 commits into
mainfrom
fix/mcp-session-cold-init

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

Every cold start of an MCP session Durable Object opened a brand-new Postgres
connection purely to re-read the organizations row the worker had already read
microseconds earlier on the same request: the authorization check resolved the
full org record and then threw the name away (organizationName: ""). On a cold
restore the DO also already held that identity in its own storage, and asked the
database first anyway.

When the database was slow to accept that connection, the read hung for the whole
connect budget and then died as an unclassified internal error — so restoring a
session failed with a bare 500 on a database that was, at that same moment,
answering the worker's own queries in milliseconds. The retry the agents SDK then
performs on the whole DO operation turned a short blip into a client-visible hang.

The store error it carried also had an empty field set: no failing operation, no
reason, so the report was undiagnosable on its own.

Fix

  • McpOrganizationAuth.authorize returns the resolved organization record rather
    than just its id; the principal and the session props carry its name and slug.
  • The session DO now prefers, in order: the props the worker just resolved → the
    meta this session already persisted (same organization only) → the database.
  • The remaining database read gets a bounded retry over transient connection
    failures and fails into the same classified, retryable 503 with Retry-After
    the auth path already returns, instead of orDie and a bare 500.
  • Hygiene: UserStoreError carries operation and a reason classified from the
    driver cause chain, and those safe fields are promoted onto error-report tags
    (the pretty cause is only an extra, which is scrubbed server-side).

Relationship to the Durable-Object error classifier

That work landed on main first (#1788) and this branch is rebased onto it.
Where the two overlapped, main's shapes are canonical and this branch's
semantics sit on top of them:

  • Single capture owner for an init defect. Both changes fixed the same
    double-report; main's fix is the one kept. The DO seam still captures and
    then claims the cause, and the host's own instrumentation drops its echo of
    a claimed cause. This branch originally removed the seam's capture instead —
    that removal is dropped, since combined with the claim it would have left an
    init failure reported by nobody.
  • The retryable envelope. A session DO that cannot reach the organization
    directory is an application failure that merely escapes through the same seam
    as a platform reset, so it keeps its own 503 envelope and its own message, and
    is checked ahead of classifyDurableObjectError rather than folded into it.
    The two vocabularies do not currently overlap; the ordering keeps it that way
    if either grows.

Testing

Primary is the new e2e scenario e2e/cloud/mcp-session-cold-init.test.ts (cloud
project), black-box over the MCP wire plus the exported spans: initialize
mints a session and the same session id then serves tools/list, and that one
init request performs exactly ONE organizations read — the worker's own
authorization check — with no database step inside the Durable Object. The count
is only taken once a worker-plane span for the same trace has landed, so an
unflushed worker batch cannot make a two-read request look like a one-read one.
Verified red against the pre-fix product code (two reads on one initialize)
and green with the fix, and green three consecutive times plus once under load
alongside the sibling MCP scenarios.

Unit tests are kept only for seams the e2e harness cannot observe: the stored-meta
and database fallback sources and the bounded retry (post-fix the props always
name the org on cloud, so no black-box request can reach those branches), the
driver-cause classification of the store error, and the restore ordering on the
base Durable Object.

A frozen-database repro was attempted and does not work here: the harness runs one
single-process PGlite shared by the worker and the DO, and the worker's own
authorization reads that same organizations row on the same request — so
freezing it fails the request before init identically on both sides of the fix,
with no green side. The read-count assertion above is the closest black-box repro
the harness allows.

Post-rebase, mcp-destroyed-session-envelope.test.ts (the classifier's own
scenario from main) passes alongside the new one, so the convergence keeps both
behaviours. typecheck, lint, format:check and the affected unit suites pass.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud c849005 Aug 28 2026, 12:04 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing c849005 Commit Preview URL

Branch Preview URL
Aug 28 2026, 12:03 AM

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1787

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1787

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1787

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1787

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1787

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1787

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1787

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1787

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1787

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1787

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1787

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1787

executor

npm i https://pkg.pr.new/executor@1787

commit: c849005

@RhysSullivan
RhysSullivan force-pushed the fix/mcp-session-cold-init branch from 0d3fa81 to c849005 Compare August 28, 2026 00:01
@RhysSullivan
RhysSullivan marked this pull request as ready for review August 28, 2026 00:43
@RhysSullivan
RhysSullivan merged commit 27b044d into main Aug 28, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant