ClementTsang/bottom · error

start paren missing

Error message

start paren missing

What it means

Process::from_file parses the Linux /proc/[pid]/stat pseudo-file, whose first field (comm, the process name) is wrapped in parentheses. The parser first locates the opening '(' with str::find; if none is present, the line cannot be a valid stat record, so the library raises this anyhow error and aborts parsing that process entry.

Solutions

  1. Ensure the path passed to Process::from_file points to a real /proc/<pid>/stat file with expected content before parsing.
  2. Handle the error gracefully and skip the process entry, since it typically means the process vanished mid-read.
  3. Check that your environment exposes /proc properly (not masked by a mount namespace, seccomp, or a stub filesystem).

Example fix

// before
let proc = Process::from_file(&stat_path).unwrap();
// after
let proc = match Process::from_file(&stat_path) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("start paren missing") => {
        eprintln!("skipping vanished process {:?}", stat_path);
        continue;
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: try-catch

Validate before calling

fn looks_like_stat(content: &str) -> bool {
    content.contains('(')
}
if !looks_like_stat(&content) { skip_entry(); }

Type guard

fn has_comm(line: &str) -> Option<&str> {
    line.find('(').map(|i| &line[..i])
}

Try / catch

match Process::from_file(path) {
    Err(e) if e.to_string().contains("start paren missing") => skip(path),
    other => other?,
}

Prevention

When it happens

Trigger: Calling Process::from_file (directly or via process enumeration on Linux) with a stat file whose contents lack an opening parenthesis — e.g. an empty file, a truncated read racing with process exit, or a path that is not actually a proc stat file.

Common situations: Enumerating /proc while processes are exiting (the file exists but content reads as empty or partially written); running inside containers or sandboxed environments where /proc is masked or stubbed; tests feeding fake stat content that omits the parenthesized comm field.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

    /// Get process stats from a file; this assumes the file is located at
    /// `/proc/<PID>/stat`. For documentation, see
    /// [here](https://manpages.ubuntu.com/manpages/noble/man5/proc_pid_stat.5.html) as a reference.
    fn from_file(mut f: File, buffer: &mut String) -> anyhow::Result<Stat> {
        // Since this is just one line, we can read it all at once. However,
        // since it (technically) might have non-utf8 characters, we
        // can't just use read_to_string.
        f.read_to_end(unsafe { buffer.as_mut_vec() })?;

        // TODO: Is this needed?
        let line = buffer.trim();

        // Comm is represented by a string in parentheses (e.g. `(foo)`,
        // `((bar))`). To handle that second case, we need to find the
        // "last" closing parentheses.
        let (comm, rest) = {
            let start_paren = line
                .find('(')
                .ok_or_else(|| anyhow!("start paren missing"))?;

            // So, we _could_ try and be smart and only parse a limited slice of
            // the string - however, there appears to be no ABI
            // guarantees of comm length anymore, so we just take the hit and do
            // an rsplit over the full string.
            //
            // Sources/discussion:
            // - https://man.archlinux.org/man/proc_pid_stat.5.en
            // - https://stackoverflow.com/questions/23534263/what-is-the-maximum-allowed-limit-on-the-length-of-a-process-name#comment138697304_23534499
            // - https://elixir.bootlin.com/linux/v7.1.3/source/fs/proc/array.c#L100
            // - https://github.com/ClementTsang/bottom/pull/2163#issuecomment-5017857303
            let (comm, rest) = line[start_paren + 1..]
                .rsplit_once(") ")
                .ok_or_else(|| anyhow!("stat string is malformed"))?;

            (comm.to_string(), rest)
        };

View on GitHub (pinned to b77d317502)