github/copilot-sdk · error
CLI initial thread was not found
Error message
CLI initial thread was not found
What it means
resume_initial_thread enumerates threads of the (suspended) CLI process via the Win32 tool-help snapshot to find and resume its initial thread. If the snapshot loop completes without matching a thread belonging to the process, it raises NotFound — the process likely exited or has no enumerable threads.
Solutions
- Confirm the CLI process is still alive (check exit code) before calling spawn-with-job helpers
- Retry the spawn; an instant-exit race is often transient (e.g. during system churn)
- Investigate why the CLI dies at startup (missing DLLs, bad config, AV)
- Verify snapshot creation succeeded and the PID passed is the CLI's own PID
Example fix
// before
resume_initial_thread(pid)?; // NotFound if process already gone
// after
if !is_process_alive(pid) {
return Err(io::Error::new(io::ErrorKind::NotFound, "CLI exited before thread enumeration"));
}
resume_initial_thread(pid)?; Defensive patterns
Strategy: retry
Try / catch
// Rust
match spawn_with_resume(cmd) {
Err(e) if e.kind() == io::ErrorKind::NotFound && attempts < 2 => retry_after_delay(),
r => r?,
} Prevention
- Retry once on transient instant-exit races
- Check the child's exit code/stderr to distinguish crash vs race
- Ensure the PID used for enumeration is the freshly spawned child's
- Test on the target Windows security posture (tool-help snapshot permissions)
When it happens
Trigger: Thread32First/Thread32Next finds no entry with th32OwnerProcessID equal to the CLI PID — the process exited before enumeration, or thread enumeration was denied/failed on a hardened system.
Common situations: CLI crashing instantly on Windows (see exit reason in logs); PID reuse or race with process death; restrictive security policy blocking tool-help snapshots.
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
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/07a676c827f80166.
Report an issue: GitHub.
Appendix: source
Thrown at rust/src/process_tree.rs:179
if entry.th32OwnerProcessID == pid {
// SAFETY: the thread id came from the live system snapshot.
let raw_thread =
unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) };
if raw_thread.is_null() {
return Err(io::Error::last_os_error());
}
let thread = OwnedHandle(raw_thread);
// SAFETY: this is the root's suspended initial thread.
if unsafe { ResumeThread(thread.0) } == u32::MAX {
return Err(io::Error::last_os_error());
}
return Ok(());
}
// SAFETY: same valid snapshot and initialized entry as above.
found = unsafe { Thread32Next(snapshot.0, &mut entry) } != 0;
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"CLI initial thread was not found",
))
}
impl Tree {
pub(super) fn terminate(&self) -> io::Result<()> {
// SAFETY: the handle is a live Job Object owned by this value.
if unsafe { TerminateJobObject(self.job.0, 1) } != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
}
View on GitHub (pinned to cd8cf15dc3)