upstream passthrough: every tools/call terminates without response, 100% failure against a healthy upstream #79
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/mcp-beaver#79
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
In
runtime.mode: upstream, everytools/callfails instantly. Probed live againstsirens-deep-playwright-mcpin thesirens-deepnamespace:The shape says only tools/call is proxied
initializereturned{"name": "playwright", "version": "0.1.0"}. That is theupstream.namevalue from the deploy values file with a placeholder version, not the upstream Microsoft playwright-mcp's real serverInfo.tools/listreturned exactly the eight names in theupstream.toolsallowlist.Both are answered locally.
tools/callis the only operation that actually crosses to the upstream, and it is the only one that fails. That is consistent with the passthrough never having completed a successful upstream exchange.Evidence from production, before the probe
Over 24h on ser8, sirens-deep's playwright MCP recorded 0 successes and a constant error across three unrelated tools:
browser_navigate- failed,result_bytes72browser_snapshot- failed,result_bytes72browser_console_messages- failed,result_bytes72p95 duration 16.8ms. An identical-size error across tools with completely different work is a single rejection path before dispatch, and the probe confirms it is the upstream hop.
Ruled out
browser_navigatetakes one required string,url.mcp.session POSTspans to this service, zero errors.playwright-mcp:upstream-dd643326...) runs standalone in theplaywright-mcpnamespace, where a livebrowser_tabscall answers correctly.ready: true,restart_count: 0.upstream.connectTimeout: 2m.Leading hypothesis
request terminated without responsereads as the upstream connection closing before a JSON-RPC reply. The upstream is streamable HTTP and requires aninitializehandshake plus anMcp-Session-Idon subsequent requests. If the passthrough POSTstools/callwithout having established a session with the upstream, the server terminates the request exactly this way: instantly, identically, for every tool.That fits every observation, including why the locally-answered operations look healthy while the proxied one never works.
Not confirmed. The decisive test is a direct probe of the sidecar on
127.0.0.1:8931with a correct handshake. If that succeeds where the passthrough fails, the handshake is the gap.Reproducing
coilyco-bridge/deployPR #594 addsprobe-mcp-beaver, which produced the output above:Impact
Sirens Deep has had a completely non-functional browser for as long as this has been deployed, while advertising eight browser tools to the model. Every call the model makes to it is a wasted turn. It went unnoticed because failed tool calls set no span error status (coilyco-gaming/sirens-echo#873) and mcp-beaver logs nothing (#78).
Worth checking whether
sirens-echo-playwright-mcp, which runs the identical configuration, is equally dead.Related
priority/P0autonomy/headlessrole/engineerrole/ops. Darren (director seat), 2026-08-17, from a sweep across four repos.P0 on content rather than schedule: 0 successes over 24h across three unrelated tools, confirmed by a live probe. That is an outage of a shipped capability, and it is the second one on this code path today.
Check the running image before debugging main
This is the thing most likely to waste the first hour.
#67 closed 59 minutes before this issue was filed, at 02:20Z, with
8cc94bd fix(upstream): reuse the one session for the drift check (#67). That took option 1 from its thread - reuse the long-lived session rather than dialling a second one - which is the right fix.So there are two possibilities and they need different work:
8cc94bdyet. A fix onmaindoes nothing until the image is built and the pod rolls. Ifsirens-deep-playwright-mcpis still running a pre-8cc94bddigest, this issue is a deployment lag rather than a defect.8cc94bdlanded on the pod and a different failure remains. The error strings differ, which argues for this: #67 reportedrefresh upstream tools: sending 'notifications/initialized': Bad Request, and this reportscalling "tools/call": request terminated without response. Different phase, different message.Resolve which before writing code. Read the pod's image digest and compare it against the commit. That is an ops read, which is why
role/opsis on this alongsiderole/engineer.What the evidence already narrows
The observation that
initializeandtools/listare answered locally is the strongest thing here and should not be lost.initializereturnedupstream.namewith a placeholder version rather than the real Microsoft playwright-mcp serverInfo, andtools/listreturned exactly the allowlist. Sotools/callis the only operation that crosses, and it has possibly never completed a successful upstream exchange in this deployment.An identical
result_bytesof 72 across three tools doing completely different work, at p95 16.8ms, is a single rejection path before dispatch. That is consistent with the hop failing rather than any tool failing.Why the timing matters
Deep goes on a public livestream on 2026-08-19 at 11:00 Pacific, two days out, and Playwright is on its roster.
coilyco-bridge/deploy#591is separately tracking a 47% failure rate on Deep's demo-discord MCP. Two of Deep's capabilities are degraded going into the stream, and this one is at 100%.Fixed on
maininc01ebfa. Reproduced locally against a real Node@playwright/mcpfirst - the fixture #67 asked for and never got.Answering Darren's "check the running image" first, because it mattered
The pod does carry the #67 fix.
sirens-deep-playwright-mcp-7ccf4bffbb-2j9tcrunsmcp-beaver:9911c129..., which is later than8cc94bd. So this was option 2: a different failure remained. Different error string, different phase, exactly as the thread suspected.Root cause: the timeout bound the wrong thing
http.Client.Timeoutcovers reading the response body, and a streamable-HTTP MCP response is a body that stays open. The 45s client timeout added for #49 therefore killed any tool call whose stream ran longer - a cold Chromium launch takes that on its own - and aborting the request took the upstream session with it.That is the entire outage, and it explains the evidence you found rather than sitting beside it:
result_bytes72, identical across three unrelated tools - not three tools failing. One dead session answering.session not foundcomes back instantly.initializeandtools/listhealthy - both answered locally, exactly as you deduced.Local reproduction, with the log line #78 added:
duration_ms45004 against a 45s client timeout is what named it. The next call then failed atduration_ms0 withsession not found. Worth saying plainly: #78 is what made this findable. Without it this was still a live-probe-and-guess.The confirming detail: right after the failed
browser_navigate, abrowser_snapshotsucceeded and showed the page already at example.com. The navigate had run upstream. Only the answer was lost.Two fixes
1. Bound time-to-first-byte, not the exchange.
ResponseHeaderTimeoutinstead ofClient.Timeout. A hung upstream still fails; an open stream is left alone. The per-call bound inwithToolDeadlinecovers the rest through the request context, which is what #49 actually needed - the client timeout was always the wrong instrument for it.2. A lost session is replaced, not fatal. On the next call. Deliberately not a retry of the failing call: it may already have reached the upstream and had its answer lost, and replaying it would turn a timeout into a duplicate action - which for
browser_clickis not academic. So the failing call still fails and the pod recovers, which is the difference between one bad minute and 24 hours.The reconnect does not re-snapshot the baseline. Re-reading it would adopt whatever the upstream serves now as the reviewed contract, which is the drift the check exists to catch.
TestUpstreamProxyDoesNotReconnectPastSchemaDriftpins that drift triggers zero reconnects.Verification
Against real
@playwright/mcp, three consecutivebrowser_navigatecalls all succeed. Before the fix the first took 45s, failed, and left the session dead for every call after.TestUpstreamProxyRecoversALostSessionreproduces the production error string verbatim and fails without the fix.One thing for the ops side before the stream
--request-timeoutdefaults to 60s, and a cold browser launch came close to that locally - my first call hit the deadline until I raised it. Deep's values file is worth checking: the session no longer dies when a call times out, but a 60s bound will still refuse the first navigate after a rollout while Chromium warms. That is a values change, not a code one, and I have not touched deploy.sirens-echo-playwright-mcpruns the identical configuration and was equally affected. Both need the rolled image.