jdx/mise · error

could not find the suspended description command's primary t

Error message

could not find the suspended description command's primary thread

What it means

Thrown by `WindowsJob::start` when, after creating the suspended description command process inside a Windows job object, a Toolhelp32 snapshot traversal (Thread32First/Thread32Next) finds no thread to identify as the command's primary thread. Without that thread handle the library cannot resume/track the process, so it aborts startup.

Source

Thrown at src/system/history/describe_command/windows_job.rs:87

        while found {
            if entry.th32OwnerProcessID == child.id() {
                // SAFETY: request only resume access to the child's thread.
                let thread = unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) };
                if thread.is_null() {
                    return Err(std::io::Error::last_os_error().into());
                }
                // SAFETY: the returned handle is valid and uniquely owned.
                let thread = unsafe { OwnedHandle::from_raw_handle(thread) };
                // SAFETY: the child is already assigned to our kill-on-close job.
                if unsafe { ResumeThread(thread.as_raw_handle()) } == u32::MAX {
                    return Err(std::io::Error::last_os_error().into());
                }
                return Ok(());
            }
            // SAFETY: the snapshot and writable entry remain valid.
            found = unsafe { Thread32Next(snapshot.as_raw_handle(), &mut entry) } != 0;
        }
        bail!("could not find the suspended description command's primary thread")
    }

    pub(super) fn kill(&self) {
        // SAFETY: the owned handle always refers to this command's job.
        unsafe { TerminateJobObject(self.0.as_raw_handle(), 1) };
    }
}

pub(super) fn spawn(command: &mut Command) -> Result<(Child, Job)> {
    let job = Job::new()?;
    command.creation_flags(CREATE_SUSPENDED);
    let mut child = command.spawn()?;
    if let Err(error) = job.start(&child) {
        // A failure must not leave either a suspended child or an unowned tree.
        let _ = child.kill();
        let _ = child.wait();
        return Err(error);
    }

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Retry the description command; a transient race with process startup is the usual cause.
  2. Check whether antivirus/EDR is killing freshly spawned processes and add an exclusion for the mise binary.
  3. Verify the command itself does not exit instantly with an error before threads can be enumerated.
  4. Report a bug if reproducible: the snapshot loop should handle this process-creation race internally.
Defensive patterns

Strategy: retry

Try / catch

// on Err("could not find ... primary thread") on Windows: retry once
// before assuming the command itself is broken

Prevention

When it happens

Trigger: On Windows only: process creation succeeded but the snapshot enumeration returned no threads belonging to the new process — typically a race where the process exited before enumeration, or snapshot creation raced process initialization.

Common situations: Antivirus or security software terminating newly spawned processes; extremely fast-failing description commands on Windows; system resource pressure making CreateToolhelp32Snapshot return a stale/empty snapshot.

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 jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/9727fe05d983121f. Report an issue: GitHub.