ClementTsang/bottom · error

PID for was not found

Error message

PID for {pid_path:?} was not found

What it means

When building a Process from a /proc/<pid> directory, the PID is recovered from the directory entry: either the file name itself or, for numeric-task threads, the target of the symlink via readlinkat. If neither yields a value parseable as a Pid, the library raises this error because a process cannot be represented without its PID.

Solutions

  1. Skip the unreadable entry during enumeration — the thread/process likely exited between listing and opening.
  2. Verify the path is a genuine /proc/<pid> or /proc/<pid>/task/<tid> directory before calling Process::from_path.
  3. Check that the process still exists (readdir race); re-scan /proc to refresh the entry list.

Example fix

// before
let proc = Process::from_path(pid_dir).unwrap();
// after
match Process::from_path(&pid_dir) {
    Ok(p) => processes.push(p),
    Err(e) if e.to_string().contains("was not found") => {
        // thread/process vanished mid-scan; skip it
    }
    Err(e) => return Err(e.into()),
}
Defensive patterns

Strategy: fallback

Validate before calling

fn pid_from_dir(path: &Path) -> Option<Pid> {
    path.file_name()
        .and_then(|n| n.to_str())
        .and_then(|n| n.parse::<Pid>().ok())
        .or_else(|| std::fs::read_link(path).ok()
            .and_then(|t| t.to_str().and_then(|s| s.parse().ok())))
}
if pid_from_dir(&dir).is_none() { skip_entry(); }

Type guard

fn dir_is_process(dir: &Path) -> bool {
    dir.file_name()
        .and_then(|n| n.to_str())
        .map(|n| n.parse::<i32>().is_some())
        .unwrap_or(false)
}

Try / catch

match Process::from_path(&pid_dir) {
    Ok(p) => Some(p),
    Err(e) if e.to_string().contains("was not found") => None,
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling Process::from_path on a directory whose name is non-numeric and whose readlink does not resolve to a numeric PID — e.g. a /proc/<pid>/task entry that vanished, a fake/stub procfs, or a directory with an unexpected name.

Common situations: Scanning /proc/[pid]/task while threads terminate (dirfd points at a removed entry); running in restricted containers where readlink fails on procfs; non-standard procfs mounts or mocked filesystems used in tests.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of ClementTsang/bottom@b77d317502 (2026-09-07). Data as JSON: /api/errors/4eec1d981eadec3a. Report an issue: GitHub.

Appendix: source

Thrown at src/collection/processes/linux/process.rs:301

        let pid_dir = rustix::fs::openat(
            rustix::fs::CWD,
            pid_path.as_path(),
            OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC,
            Mode::empty(),
        )?;

        let pid = pid_path
            .as_path()
            .components()
            .next_back()
            .and_then(|s| s.to_string_lossy().parse::<Pid>().ok())
            .or_else(|| {
                rustix::fs::readlinkat(rustix::fs::CWD, pid_path.as_path(), vec![])
                    .ok()
                    .and_then(|s| s.to_string_lossy().parse::<Pid>().ok())
            })
            .ok_or_else(|| anyhow!("PID for {pid_path:?} was not found"))?;

        let uid = {
            let metadata = rustix::fs::fstat(&pid_dir);
            match metadata {
                Ok(md) => Some(md.st_uid),
                Err(_) => None,
            }
        };

        let mut root = pid_path;

        // NB: Whenever you add a new stat, make sure to pop the root and clear
        // the buffer!

        // Stat is pretty long, do this first to pre-allocate up-front.
        let stat =
            open_at(&mut root, "stat", &pid_dir).and_then(|file| Stat::from_file(file, buffer))?;
        reset(&mut root, buffer);

View on GitHub (pinned to b77d317502)