{"record":{"id":"bfced1f535d7812e","repo":"github/copilot-sdk","slug":"cli-exited-before-thread-resume","errorCode":null,"errorMessage":"CLI exited before thread resume","messagePattern":"CLI exited before thread resume","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/src/process_tree.rs","lineNumber":140,"sourceCode":"            )\n        } == 0\n        {\n            return Err(io::Error::last_os_error());\n        }\n\n        let process = child.raw_handle().ok_or_else(|| {\n            io::Error::new(\n                io::ErrorKind::NotFound,\n                \"CLI exited before Job Object assignment\",\n            )\n        })?;\n        // SAFETY: both handles are valid and the child is still suspended.\n        if unsafe { AssignProcessToJobObject(job.0, process.cast()) } == 0 {\n            return Err(io::Error::last_os_error());\n        }\n\n        resume_initial_thread(child.id().ok_or_else(|| {\n            io::Error::new(io::ErrorKind::NotFound, \"CLI exited before thread resume\")\n        })?)?;\n        Ok(Tree { job })\n    }\n\n    fn resume_initial_thread(pid: u32) -> io::Result<()> {\n        // SAFETY: the returned snapshot handle is owned and closed below.\n        let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };\n        if snapshot == INVALID_HANDLE_VALUE {\n            return Err(io::Error::last_os_error());\n        }\n        let snapshot = OwnedHandle(snapshot);\n        let mut entry = THREADENTRY32 {\n            dwSize: size_of::<THREADENTRY32>() as u32,\n            ..Default::default()\n        };\n\n        // SAFETY: `entry` has the documented size and remains live throughout\n        // enumeration.","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/rust/src/process_tree.rs#L122-L158","documentation":"After assigning the suspended CLI process to the Job Object, attach_and_resume must resume its initial thread. child.id() returning None means the process already exited before resume, so the code raises NotFound rather than calling resume with a dead PID.","triggerScenarios":"The suspended CLI process dies between AssignProcessToJobObject and the thread-resume step — an immediate crash, missing dependency, or external termination (antivirus, job limits).","commonSituations":"Broken CLI installation on Windows; security software killing the process; Job Object limits (e.g. JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE misconfiguration) terminating it early; incompatible CLI binary.","solutions":["Run the CLI manually to see its real startup failure (exit code, stderr)","Check Event Viewer and antivirus logs for process termination","Validate the Job Object configuration doesn't kill the process prematurely","Reinstall/update the Copilot CLI to a compatible version"],"exampleFix":"// before\nlet tree = process_tree::spawn(cmd)?; // NotFound: CLI exited before thread resume\n// after\nmatch process_tree::spawn(cmd) {\n    Err(e) if e.kind() == io::ErrorKind::NotFound => {\n        eprintln!(\"CLI failed to start; verify installation: {e}\");\n        std::process::exit(1);\n    }\n    r => r?,\n}","handlingStrategy":"validation","validationCode":"// Rust\nif !cli_binary_runs(cmd) { return Err(io::Error::new(io::ErrorKind::NotFound, \"CLI binary cannot start\")); }","typeGuard":null,"tryCatchPattern":"// Rust\nif let Err(e) = process_tree::spawn(cmd) {\n    if e.kind() == io::ErrorKind::NotFound { log_cli_exit_reason(); }\n    return Err(e);\n}","preventionTips":["Run the CLI manually to capture its real startup error","Review Windows Event Viewer for silent process terminations","Validate Job Object limit flags don't kill the child early","Reinstall/update the CLI if startup is consistently instant-fail"],"tags":["windows","process-spawn","win32","race-condition"],"backgroundTag":"resource-not-found","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}