{"record":{"id":"f7acb5dada5c09cc","repo":"Hmbown/CodeWhale","slug":"suspended-mcp-child-had-no-resumable-thread","errorCode":null,"errorMessage":"suspended MCP child had no resumable thread","messagePattern":"suspended MCP child had no resumable thread","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mcp/src/stdio_client.rs","lineNumber":835,"sourceCode":"        let mut entry = THREADENTRY32 {\n            dwSize: std::mem::size_of::<THREADENTRY32>() as u32,\n            ..Default::default()\n        };\n        let mut next = Thread32First(HANDLE(snapshot.as_raw_handle()), &mut entry);\n        let mut resumed = 0usize;\n        while next.is_ok() {\n            if entry.th32OwnerProcessID == child.id() {\n                let thread = OpenThread(THREAD_SUSPEND_RESUME, false, entry.th32ThreadID)?;\n                let thread = OwnedHandle::from_raw_handle(thread.0);\n                if ResumeThread(HANDLE(thread.as_raw_handle())) == u32::MAX {\n                    return Err(io::Error::last_os_error().into());\n                }\n                resumed += 1;\n            }\n            next = Thread32Next(HANDLE(snapshot.as_raw_handle()), &mut entry);\n        }\n        if resumed == 0 {\n            bail!(\"suspended MCP child had no resumable thread\");\n        }\n        Ok(job)\n    }\n}\n\nimpl Connection {\n    fn send(&mut self, message: &Value) -> Result<()> {\n        let stdin = self\n            .stdin\n            .as_ref()\n            .context(\"connection stdin already closed\")?;\n        let mut stdin = stdin\n            .lock()\n            .map_err(|_| anyhow!(\"connection stdin poisoned by an earlier panic\"))?;\n        write_jsonrpc_line(&mut *stdin, message)\n    }\n\n    fn request(","sourceCodeStart":817,"sourceCodeEnd":853,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/mcp/src/stdio_client.rs#L817-L853","documentation":"On Windows, contain_windows_child suspends all threads of the freshly spawned MCP child via a tool-help snapshot, then resumes them after attaching the job object. If it found no threads it could resume (resumed == 0), it fails — the child would otherwise be left suspended forever or the job containment would be unreliable.","triggerScenarios":"Windows-only: spawning an MCP server whose process exits or whose threads disappear between snapshot creation and Thread32First iteration, so the snapshot contains zero resumable threads.","commonSituations":"Child crashing instantly on launch (bad interpreter, missing DLL); race where the process terminated before enumeration; security software interfering with the new process; shell wrappers that re-exec.","solutions":["Run the server command directly instead of through a shell/cmd wrapper that re-execs.","Verify the command itself works outside the MCP client (missing DLLs or bad interpreter cause instant exit).","Retry the spawn — this can be a startup race with a fast-crashing child.","Check antivirus/EDR logs for interference with the child process.","Report upstream if a legitimately short-lived-but-alive child consistently trips this."],"exampleFix":"// before (config)\ncommand: \"cmd /c server.exe\"\n// after\ncommand: \"C:\\\\path\\\\server.exe\"","handlingStrategy":"retry","validationCode":"// Windows: check the executable exists and is a native binary before spawning\nuse std::path::Path;\nfn command_spawnable(cfg: &McpServerConfig) -> bool {\n    Path::new(&cfg.command).is_file() || which::which(&cfg.command).is_ok()\n}","typeGuard":null,"tryCatchPattern":"let client = match spawn_with_timeouts(&config, handshake, request_timeout) {\n    Ok(c) => c,\n    Err(e) if e.to_string().contains(\"no resumable thread\") && attempt < 2 => {\n        tokio::time::sleep(Duration::from_millis(250)).await;\n        continue; // retry spawn; likely a startup race\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Spawn native executables directly rather than via cmd/shell wrappers that re-exec.","Test the command manually on Windows before registering it as an MCP server.","Check antivirus/EDR policies for interference with newly spawned processes.","If a child crashes instantly, fix the crash — retry loops cannot save a broken binary."],"tags":["windows","process","mcp"],"backgroundTag":"internal-invariant-violation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}