{"record":{"id":"78f695fa686d7c9d","repo":"ClementTsang/bottom","slug":"start-paren-missing","errorCode":null,"errorMessage":"start paren missing","messagePattern":"start paren missing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/collection/processes/linux/process.rs","lineNumber":93,"sourceCode":"    /// Get process stats from a file; this assumes the file is located at\n    /// `/proc/<PID>/stat`. For documentation, see\n    /// [here](https://manpages.ubuntu.com/manpages/noble/man5/proc_pid_stat.5.html) as a reference.\n    fn from_file(mut f: File, buffer: &mut String) -> anyhow::Result<Stat> {\n        // Since this is just one line, we can read it all at once. However,\n        // since it (technically) might have non-utf8 characters, we\n        // can't just use read_to_string.\n        f.read_to_end(unsafe { buffer.as_mut_vec() })?;\n\n        // TODO: Is this needed?\n        let line = buffer.trim();\n\n        // Comm is represented by a string in parentheses (e.g. `(foo)`,\n        // `((bar))`). To handle that second case, we need to find the\n        // \"last\" closing parentheses.\n        let (comm, rest) = {\n            let start_paren = line\n                .find('(')\n                .ok_or_else(|| anyhow!(\"start paren missing\"))?;\n\n            // So, we _could_ try and be smart and only parse a limited slice of\n            // the string - however, there appears to be no ABI\n            // guarantees of comm length anymore, so we just take the hit and do\n            // an rsplit over the full string.\n            //\n            // Sources/discussion:\n            // - https://man.archlinux.org/man/proc_pid_stat.5.en\n            // - https://stackoverflow.com/questions/23534263/what-is-the-maximum-allowed-limit-on-the-length-of-a-process-name#comment138697304_23534499\n            // - https://elixir.bootlin.com/linux/v7.1.3/source/fs/proc/array.c#L100\n            // - https://github.com/ClementTsang/bottom/pull/2163#issuecomment-5017857303\n            let (comm, rest) = line[start_paren + 1..]\n                .rsplit_once(\") \")\n                .ok_or_else(|| anyhow!(\"stat string is malformed\"))?;\n\n            (comm.to_string(), rest)\n        };\n","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/ClementTsang/bottom/blob/b77d3175028849824e987c35177e8f61450d72e7/src/collection/processes/linux/process.rs#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nlet proc = Process::from_file(&stat_path).unwrap();\n// after\nlet proc = match Process::from_file(&stat_path) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"start paren missing\") => {\n        eprintln!(\"skipping vanished process {:?}\", stat_path);\n        continue;\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":"fn looks_like_stat(content: &str) -> bool {\n    content.contains('(')\n}\nif !looks_like_stat(&content) { skip_entry(); }","typeGuard":"fn has_comm(line: &str) -> Option<&str> {\n    line.find('(').map(|i| &line[..i])\n}","tryCatchPattern":"match Process::from_file(path) {\n    Err(e) if e.to_string().contains(\"start paren missing\") => skip(path),\n    other => other?,\n}","preventionTips":["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"],"tags":["linux","procfs","parsing","processes"],"backgroundTag":"invalid-argument-format","analyzedSha":"b77d3175028849824e987c35177e8f61450d72e7","analyzedAt":"2026-09-07T14:53:21.246Z","contentChangedAt":"2026-09-07T14:53:21.246Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}