{"record":{"id":"9727fe05d983121f","repo":"jdx/mise","slug":"could-not-find-the-suspended-description-command-s","errorCode":null,"errorMessage":"could not find the suspended description command's primary thread","messagePattern":"could not find the suspended description command's primary thread","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/describe_command/windows_job.rs","lineNumber":87,"sourceCode":"        while found {\n            if entry.th32OwnerProcessID == child.id() {\n                // SAFETY: request only resume access to the child's thread.\n                let thread = unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) };\n                if thread.is_null() {\n                    return Err(std::io::Error::last_os_error().into());\n                }\n                // SAFETY: the returned handle is valid and uniquely owned.\n                let thread = unsafe { OwnedHandle::from_raw_handle(thread) };\n                // SAFETY: the child is already assigned to our kill-on-close job.\n                if unsafe { ResumeThread(thread.as_raw_handle()) } == u32::MAX {\n                    return Err(std::io::Error::last_os_error().into());\n                }\n                return Ok(());\n            }\n            // SAFETY: the snapshot and writable entry remain valid.\n            found = unsafe { Thread32Next(snapshot.as_raw_handle(), &mut entry) } != 0;\n        }\n        bail!(\"could not find the suspended description command's primary thread\")\n    }\n\n    pub(super) fn kill(&self) {\n        // SAFETY: the owned handle always refers to this command's job.\n        unsafe { TerminateJobObject(self.0.as_raw_handle(), 1) };\n    }\n}\n\npub(super) fn spawn(command: &mut Command) -> Result<(Child, Job)> {\n    let job = Job::new()?;\n    command.creation_flags(CREATE_SUSPENDED);\n    let mut child = command.spawn()?;\n    if let Err(error) = job.start(&child) {\n        // A failure must not leave either a suspended child or an unowned tree.\n        let _ = child.kill();\n        let _ = child.wait();\n        return Err(error);\n    }","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/describe_command/windows_job.rs#L69-L105","documentation":"On Windows, the history 'describe command' runs inside a job object and its initial thread is created suspended so it can be assigned to the job before it runs. This error is thrown by `start` when it enumerates the process's threads with the tool-help snapshot API (Thread32First/Thread32Next) and cannot find the expected primary suspended thread, so the command can never be safely started.","triggerScenarios":"Calling the Windows-only describe-command start path when the child process already exited before the thread snapshot was taken, when the PID no longer has any threads, or when the tool-help snapshot enumerates no matching thread (e.g. race between CreateProcess and Thread32First).","commonSituations":"Very short-lived commands that exit before the suspended primary thread is located; heavily loaded or restricted systems where process/thread enumeration fails transiently; running under environments that interfere with tool-help snapshots (some sandboxes or job restrictions).","solutions":["Retry the command start; the failure is usually a race with immediate child exit","Ensure the described command is not a batch/instant-exit wrapper that terminates before thread enumeration","Check that the process is running under a normal (non-restricted) environment and no sandbox policy is hiding threads","If reproducible, capture the child PID and inspect with `tasklist /v` / Process Explorer to confirm the process is alive at start time"],"exampleFix":"// before\nbail!(\"could not find the suspended description command's primary thread\")\n// after\n// retry once before failing\nif start_attempt == 0 { return start(); }\nbail!(\"could not find the suspended description command's primary thread\")","handlingStrategy":"retry","validationCode":"// before starting, confirm the child still has threads\nfn child_alive(pid: u32) -> bool {\n  std::path::Path::new(\"\\\\\\\\.\\\\Device\\\\\")\n    .exists() || unsafe {\n    let h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);\n    let ok = h != std::ptr::null_mut();\n    if ok { CloseHandle(h); }\n    ok\n  }\n}","typeGuard":null,"tryCatchPattern":"// retry the start once on thread-lookup races\nmatch start() {\n  Err(e) if e.to_string().contains(\"primary thread\") && attempt == 0 => start(),\n  other => other,\n}","preventionTips":["Avoid describing commands that exit within microseconds (wrap them so they live briefly)","Retry transient thread-enumeration failures once before surfacing the error","Test on a non-restricted environment; sandbox policies can hide threads from tool-help snapshots"],"tags":["windows","process","threading","race-condition"],"backgroundTag":"internal-invariant-violation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}