{"record":{"id":"07a676c827f80166","repo":"github/copilot-sdk","slug":"cli-initial-thread-was-not-found","errorCode":null,"errorMessage":"CLI initial thread was not found","messagePattern":"CLI initial thread was not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/src/process_tree.rs","lineNumber":179,"sourceCode":"            if entry.th32OwnerProcessID == pid {\n                // SAFETY: the thread id came from the live system snapshot.\n                let raw_thread =\n                    unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) };\n                if raw_thread.is_null() {\n                    return Err(io::Error::last_os_error());\n                }\n                let thread = OwnedHandle(raw_thread);\n                // SAFETY: this is the root's suspended initial thread.\n                if unsafe { ResumeThread(thread.0) } == u32::MAX {\n                    return Err(io::Error::last_os_error());\n                }\n                return Ok(());\n            }\n            // SAFETY: same valid snapshot and initialized entry as above.\n            found = unsafe { Thread32Next(snapshot.0, &mut entry) } != 0;\n        }\n\n        Err(io::Error::new(\n            io::ErrorKind::NotFound,\n            \"CLI initial thread was not found\",\n        ))\n    }\n\n    impl Tree {\n        pub(super) fn terminate(&self) -> io::Result<()> {\n            // SAFETY: the handle is a live Job Object owned by this value.\n            if unsafe { TerminateJobObject(self.job.0, 1) } != 0 {\n                Ok(())\n            } else {\n                Err(io::Error::last_os_error())\n            }\n        }\n    }\n}\n","sourceCodeStart":161,"sourceCodeEnd":196,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/rust/src/process_tree.rs#L161-L196","documentation":"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.","triggerScenarios":"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.","commonSituations":"CLI crashing instantly on Windows (see exit reason in logs); PID reuse or race with process death; restrictive security policy blocking tool-help snapshots.","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"],"exampleFix":"// before\nresume_initial_thread(pid)?; // NotFound if process already gone\n// after\nif !is_process_alive(pid) {\n    return Err(io::Error::new(io::ErrorKind::NotFound, \"CLI exited before thread enumeration\"));\n}\nresume_initial_thread(pid)?;","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust\nmatch spawn_with_resume(cmd) {\n    Err(e) if e.kind() == io::ErrorKind::NotFound && attempts < 2 => retry_after_delay(),\n    r => r?,\n}","preventionTips":["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)"],"tags":["windows","process-spawn","win32","threads"],"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"}