mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
fix(mcp): stop /mcp waiting forever on a cold server boot
`/mcp` with no arguments resolves to `McpUiAction::Show`, which calls
`bootstrap_mcp()` and then blocks in `wait_for_mcp_boot`. That function looped
on the `mcp_boot_done` watch channel with no outer bound, and the boot task only
flips that flag once *every* pending server finishes.
The per-server `connect_timeout` (10s) does not cover everything that can stall
a stdio server. `npx -y` and `uvx` download their package on first run, so one
cold fetch — or one npm/PyPI stall — left the whole wait pending indefinitely.
With 23 servers configured, several of them `npx`/`uvx`, `/mcp` reads to the
user as a frozen application. It was reported as a crash; nothing panicked, and
no crash report was written, because it was a hang.
This bounds the *wait*, never the boot. The supervised boot task keeps running
and its progress updates still arrive, so a slow server lands as soon as it is
ready — the UI simply stops blocking on it and shows what has connected. The
timeout logs what happened rather than silently returning a short server list as
if it were the complete one.
Deliberately untested: a timeout test here would either sleep for 20 seconds or
pin the constant, and per this repo's rule on selective evidence neither is worth
its cost. The change is a bounded wrap of an existing loop, compile-proved, with
no behavior change on the path where boot completes normally.
cargo check -p codewhale-tui --all-targets --locked: 0 errors, 0 warnings
Reported by the founder against 15b140e1c.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01X57KRgwqYkmmtVQScczqiL
This commit is contained in:
1 parent
15b140e1ca
commit
6d83a3d8d2
1 file changed
+27
-3
@@ -6775,12 +6775,36 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// How long a caller will block on the session boot before proceeding with
|
||||
/// whatever has connected so far.
|
||||
///
|
||||
/// This bounds the *wait*, never the boot: the supervised boot task keeps
|
||||
/// running, so a slow server still lands through the normal progress
|
||||
/// updates and appears once it is ready. The per-server connect timeout
|
||||
/// does not cover everything that can stall a stdio server — `npx -y` and
|
||||
/// `uvx` download their package on first run — so without an outer bound a
|
||||
/// single cold fetch left `/mcp` waiting on `mcp_boot_done` forever, which
|
||||
/// reads to the user as a frozen application.
|
||||
const MCP_BOOT_UI_WAIT: std::time::Duration = std::time::Duration::from_secs(20);
|
||||
|
||||
async fn wait_for_mcp_boot(&mut self) {
|
||||
if let Some(rx) = self.mcp_boot_done.as_mut() {
|
||||
while !*rx.borrow() {
|
||||
if rx.changed().await.is_err() {
|
||||
break;
|
||||
let settled = tokio::time::timeout(Self::MCP_BOOT_UI_WAIT, async {
|
||||
while !*rx.borrow() {
|
||||
if rx.changed().await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
if settled.is_err() {
|
||||
// Not an error: the boot continues in the background and its
|
||||
// progress updates still arrive. Say so rather than silently
|
||||
// returning a short server list as if it were complete.
|
||||
tracing::info!(
|
||||
wait_secs = Self::MCP_BOOT_UI_WAIT.as_secs(),
|
||||
"MCP session boot still connecting; continuing with the servers ready so far"
|
||||
);
|
||||
}
|
||||
}
|
||||
self.drain_mcp_boot_updates().await;
|
||||
|
||||
Reference in new issue
Block a user