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
- Ensure the path passed to Process::from_file points to a real /proc/<pid>/stat file with expected content before parsing.
- Handle the error gracefully and skip the process entry, since it typically means the process vanished mid-read.
- 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
- Skip entries whose stat content is empty or unparseable instead of propagating the error
- Re-check process existence (kill(pid,0) or path stat) before parsing
- Do not assume /proc contents are stable; treat every read as racy
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)