{"record":{"id":"10004425df135a41","repo":"github/copilot-sdk","slug":"cli-exited-before-job-object-assignment","errorCode":null,"errorMessage":"CLI exited before Job Object assignment","messagePattern":"CLI exited before Job Object assignment","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/src/process_tree.rs","lineNumber":129,"sourceCode":"        let job = OwnedHandle(raw_job);\n\n        let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();\n        limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;\n        // SAFETY: `limits` has the layout required by the selected info class.\n        if unsafe {\n            SetInformationJobObject(\n                job.0,\n                JobObjectExtendedLimitInformation,\n                ptr::from_ref(&limits).cast(),\n                size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,\n            )\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) };","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/rust/src/process_tree.rs#L111-L147","documentation":"On Windows, attach_and_resume spawns the CLI suspended and must assign it to a Job Object before resuming it. child.raw_handle() returns None if the process already exited while suspended, so the code raises NotFound with this message instead of passing a null handle into unsafe Win32 APIs.","triggerScenarios":"The spawned CLI process terminates between CreateProcess (suspended) and the AssignProcessToJobObject step — bad executable, missing DLLs, instant crash, or an antivirus killing the process.","commonSituations":"Wrong CLI path or corrupted install on Windows; missing runtime dependencies (e.g. WebView2/VC runtime); security software terminating the newly spawned process; incompatible CLI version.","solutions":["Verify the CLI executable path and that it runs standalone (check for missing DLLs/errors)","Check Windows Event Viewer / stderr for the process's actual exit reason","Exclude the CLI from antivirus/EDR interference or whitelist it","Confirm CLI version compatibility with the SDK"],"exampleFix":"// before\nlet child = Command::new(cli_path).spawn()?; // may exit instantly\n// after\nlet cli_path = which(\"copilot\").expect(\"CLI not found; install or fix PATH\");\nlet child = Command::new(cli_path).spawn()?;","handlingStrategy":"validation","validationCode":"// Rust (before spawning)\nlet cli_path = which::which(\"copilot\")?; // fail fast if CLI missing\nstd::process::Command::new(&cli_path).arg(\"--version\").status()?; // confirm it runs","typeGuard":null,"tryCatchPattern":"// Rust\nmatch process_tree::spawn(cmd) {\n    Err(e) if e.kind() == io::ErrorKind::NotFound => eprintln!(\"CLI died at spawn: check install/AV logs\"),\n    r => r?,\n}","preventionTips":["Verify the CLI binary runs standalone before programmatic spawn","Check for missing DLLs / VC runtime on target Windows machines","Whitelist the CLI in antivirus/EDR software","Pin a CLI version compatible with the SDK"],"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"}