github/copilot-sdk · error
CLI exited before Job Object assignment
Error message
CLI exited before Job Object assignment
What it means
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.
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
Example fix
// before
let child = Command::new(cli_path).spawn()?; // may exit instantly
// after
let cli_path = which("copilot").expect("CLI not found; install or fix PATH");
let child = Command::new(cli_path).spawn()?; Defensive patterns
Strategy: validation
Validate before calling
// Rust (before spawning)
let cli_path = which::which("copilot")?; // fail fast if CLI missing
std::process::Command::new(&cli_path).arg("--version").status()?; // confirm it runs Try / catch
// Rust
match process_tree::spawn(cmd) {
Err(e) if e.kind() == io::ErrorKind::NotFound => eprintln!("CLI died at spawn: check install/AV logs"),
r => r?,
} Prevention
- 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
When it happens
Trigger: The spawned CLI process terminates between CreateProcess (suspended) and the AssignProcessToJobObject step — bad executable, missing DLLs, instant crash, or an antivirus killing the process.
Common situations: 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.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- CLI exited before thread resume
- CLI initial thread was not found
- checking existing permissions
- CLI process not started
- Concurrent extraction race: target already exists but is…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/10004425df135a41.
Report an issue: GitHub.
Appendix: source
Thrown at rust/src/process_tree.rs:129
let job = OwnedHandle(raw_job);
let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
// SAFETY: `limits` has the layout required by the selected info class.
if unsafe {
SetInformationJobObject(
job.0,
JobObjectExtendedLimitInformation,
ptr::from_ref(&limits).cast(),
size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
} == 0
{
return Err(io::Error::last_os_error());
}
let process = child.raw_handle().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"CLI exited before Job Object assignment",
)
})?;
// SAFETY: both handles are valid and the child is still suspended.
if unsafe { AssignProcessToJobObject(job.0, process.cast()) } == 0 {
return Err(io::Error::last_os_error());
}
resume_initial_thread(child.id().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "CLI exited before thread resume")
})?)?;
Ok(Tree { job })
}
fn resume_initial_thread(pid: u32) -> io::Result<()> {
// SAFETY: the returned snapshot handle is owned and closed below.
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };View on GitHub (pinned to cd8cf15dc3)