You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
MCP stdio: a data call landing between the transport attach and kernel:bootstrapped still memoizes a pre-bind localization for the life of the transport #11622
Found while implementing #11580 (PR on claude/issue-11580-mcp-stdio-settings-bind-window). Not fixed there — out of that card's scope, and closing it is a design choice with a cost, not a mechanical follow-on.
#11580 moved the stdio transport's localization read out of MCPServerPlugin.start(). It now resolves from a kernel:bootstrapped hook — strictly after SettingsServicePlugin binds its data engine (kernel:ready) — memoized so it stays one resolution for the life of the transport (#7279).
The memo has a second, lazy entry point: resolvePrincipal() awaits the same memo, so a read that arrives before the hook fires resolves it then instead of deadlocking on a hook a bare host may never fire. That lazy entry is deliberate, and it is also the residual:
MCPServerPlugin.start() ← runtime.start() attaches StdioServerTransport to stdin/stdout
… other plugins' start() …
kernel:ready handlers ← SettingsServicePlugin binds the engine HERE
kernel:bootstrapped handlers ← the localization read HERE
The transport is live from the first line. A client that sends initialize + a data call while the boot is still working through the remaining start() bodies and every kernel:ready handler (schema sync, backfills — seconds on a real deployment) reaches resolvePrincipal() first. That call resolves localization pre-bind, gets UTC / en-US from the manifest defaults, and memoizes it — so the narrow race turns into the permanent wrong value #11580 was about, for that process.
Why it was left
Both cheap closures have a real cost, and picking one is a judgement, not a repair:
Make the early call WAIT for the bind (await a deferred resolved by the hook). Correct value always; introduces a hang on any host that never fires kernel:bootstrapped — worse than a wrong locale.
Do not attach the transport until kernel:bootstrapped. Closes the window at the source and is arguably where the transport belongs, but it changes when the stdio server starts answering at all — a visible behaviour change on the MCP surface.
Reproduction sketch
No test in the tree exercises it: packages/mcp/src/__tests__/plugin-settings-bind-window.test.ts drives every read after bootstrap() resolves. Reproducing it needs a read issued between MCPServerRuntime.start() and the kernel:bootstrapped dispatch — e.g. a kernel:ready handler registered after the settings provider's that calls the record reader.
Scope note
Narrow: needs a client fast enough to land inside the boot, and only bites deployments whose persisted localization differs from the platform defaults. Filed rather than fixed so the choice above is made deliberately.
Found while implementing #11580 (PR on
claude/issue-11580-mcp-stdio-settings-bind-window). Not fixed there — out of that card's scope, and closing it is a design choice with a cost, not a mechanical follow-on.What #11580 fixed, and what it left
#11580 moved the stdio transport's localization read out of
MCPServerPlugin.start(). It now resolves from akernel:bootstrappedhook — strictly afterSettingsServicePluginbinds its data engine (kernel:ready) — memoized so it stays one resolution for the life of the transport (#7279).The memo has a second, lazy entry point:
resolvePrincipal()awaits the same memo, so a read that arrives before the hook fires resolves it then instead of deadlocking on a hook a bare host may never fire. That lazy entry is deliberate, and it is also the residual:The transport is live from the first line. A client that sends
initialize+ a data call while the boot is still working through the remainingstart()bodies and everykernel:readyhandler (schema sync, backfills — seconds on a real deployment) reachesresolvePrincipal()first. That call resolves localization pre-bind, getsUTC/en-USfrom the manifest defaults, and memoizes it — so the narrow race turns into the permanent wrong value #11580 was about, for that process.Why it was left
Both cheap closures have a real cost, and picking one is a judgement, not a repair:
kernel:bootstrapped— worse than a wrong locale.tabPermissions/accessToken#7279's "one resolution for the life of the transport" conditional on a hook firing, which several bare/lite hosts do not.kernel:bootstrapped. Closes the window at the source and is arguably where the transport belongs, but it changes when the stdio server starts answering at all — a visible behaviour change on the MCP surface.Reproduction sketch
No test in the tree exercises it:
packages/mcp/src/__tests__/plugin-settings-bind-window.test.tsdrives every read afterbootstrap()resolves. Reproducing it needs a read issued betweenMCPServerRuntime.start()and thekernel:bootstrappeddispatch — e.g. akernel:readyhandler registered after the settings provider's that calls the record reader.Scope note
Narrow: needs a client fast enough to land inside the boot, and only bites deployments whose persisted
localizationdiffers from the platform defaults. Filed rather than fixed so the choice above is made deliberately.