uutils/coreutils · warning · io::Error

error-no-such-process

Error message

error-no-such-process

What it means

On Windows, process termination code constructs a NotFound io::Error with the localized 'error-no-such-process' message when it can be established that the target PID has already exited. probe_pid distinguishes 'already exited' from access denial using PROBE_ACCESS (PROCESS_SYNCHRONIZE).

Solutions

  1. Treat this NotFound error as success when the goal was termination
  2. Check existence first (probe the PID) before signaling
  3. Re-enumerate the process list to get a fresh PID

Example fix

// before
kill(pid)?;
// after
if let Err(e) = kill(pid) { if e.kind() != io::ErrorKind::NotFound { return Err(e); } }
Defensive patterns

Strategy: try-catch

Validate before calling

// windows: check existence before signaling
fn pid_alive(pid: u32) -> bool {
    std::process::Command::new("tasklist").args(["/FI", &format!("PID eq {pid}")]).output().map(|o| o.status.success()).unwrap_or(false)
}

Try / catch

match kill(pid) {
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => { /* already exited: treat as success */ }
    r => r?,
}

Prevention

When it happens

Trigger: Calling kill/terminate on a PID that no longer exists, or a PID whose process exited between lookup and TerminateProcess (the subsequent ERROR_ACCESS_DENIED is disambiguated by probing with PROCESS_SYNCHRONIZE).

Common situations: Race conditions when killing short-lived processes; scripts computing PIDs from stale data; retry loops signaling processes that already finished.

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 uutils/coreutils@92995feca2 (2026-09-20). Data as JSON: /api/errors/8dd4467172931821. Report an issue: GitHub.

Appendix: source

Thrown at src/uucore/src/lib/features/process/windows.rs:481

        Disposition::Probe => match sys::wait_for_one(child.as_handle(), 0)? {
            sys::WaitOutcome::TimedOut => Ok(()),
            // The process has exited: the POSIX analog is ESRCH.
            sys::WaitOutcome::Object(_) => Err(io::ErrorKind::NotFound.into()),
        },
        Disposition::Ignore => Ok(()),
        Disposition::Interrupt | Disposition::Terminate => {
            terminate_with_signal(child.as_handle(), signal)
        }
    }
}

const PROBE_ACCESS: u32 = PROCESS_SYNCHRONIZE;
// SYNCHRONIZE tells "already exited" apart from a real denial after a failed
// TerminateProcess (both report ERROR_ACCESS_DENIED).
const TERMINATE_ACCESS: u32 = PROCESS_TERMINATE | PROCESS_SYNCHRONIZE;

fn no_such_process() -> io::Error {
    io::Error::new(io::ErrorKind::NotFound, translate!("error-no-such-process"))
}

/// A process handle becomes signaled when the process exits.
fn has_exited(handle: BorrowedHandle) -> io::Result<bool> {
    Ok(matches!(
        sys::wait_for_one(handle, 0)?,
        sys::WaitOutcome::Object(_)
    ))
}

/// Request `SeDebugPrivilege` once per process; with it, opening a process
/// bypasses the target's security descriptor.
///
/// Silent and best-effort: only an elevated administrator's token holds the
/// privilege, and tokens without it are left untouched.
pub fn enable_debug_privilege() {
    static REQUESTED: OnceLock<()> = OnceLock::new();
    REQUESTED.get_or_init(|| {

View on GitHub (pinned to 92995feca2)