{"record":{"id":"baaaae86f1976445","repo":"gastownhall/beads","slug":"procid-malformed-proc-stat-missing-comm-terminat","errorCode":null,"errorMessage":"procid: malformed proc stat: missing comm terminator","messagePattern":"procid: malformed proc stat: missing comm terminator","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/procid/procid_linux.go","lineNumber":203,"sourceCode":"\t}\n}\n\nfunc isFatalSignal(sig syscall.Signal) bool {\n\treturn sig == syscall.SIGKILL || sig == syscall.SIGTERM\n}\n\nfunc processStartTime(pid int) (string, error) {\n\tdata, err := os.ReadFile(\"/proc/\" + strconv.Itoa(pid) + \"/stat\")\n\tif err != nil {\n\t\treturn \"\", &processStatReadError{pid: pid, err: err}\n\t}\n\treturn parseStartTime(string(data))\n}\n\nfunc parseStartTime(stat string) (string, error) {\n\tendComm := strings.LastIndex(stat, \")\")\n\tif endComm == -1 {\n\t\treturn \"\", errors.New(\"procid: malformed proc stat: missing comm terminator\")\n\t}\n\tfields := strings.Fields(stat[endComm+1:])\n\t// The remainder starts with state (field 3), so starttime (field 22) is\n\t// its twentieth field.\n\tif len(fields) < 20 {\n\t\treturn \"\", errors.New(\"procid: malformed proc stat: missing starttime\")\n\t}\n\tif fields[0] == \"Z\" || fields[0] == \"X\" || fields[0] == \"x\" {\n\t\treturn \"\", fmt.Errorf(\"procid: process is no longer running: %w\", unix.ESRCH)\n\t}\n\tif _, err := strconv.ParseUint(fields[19], 10, 64); err != nil {\n\t\treturn \"\", fmt.Errorf(\"procid: malformed proc stat starttime: %w\", err)\n\t}\n\treturn fields[19], nil\n}\n\ntype bootIDReadError struct {\n\tpath string","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/procid/procid_linux.go#L185-L221","documentation":"parseStartTime parses /proc/<pid>/stat to extract field 22 (starttime). The kernel splits the stat line after the comm field, which is wrapped in parentheses; comm can contain spaces, so the parser uses the LAST ')' as the terminator. This error means no ')' was found at all, so the read stat content is not a well-formed stat line.","triggerScenarios":"Calling processStartTime (directly or via an anonymous caller) for a PID whose /proc/<pid>/stat read returned malformed or truncated content — typically a race where the process exits and the kernel zeroes/truncates the buffer mid-read, or reading the wrong file entirely.","commonSituations":"Short-lived child processes exiting between open() and read() of /proc; containerized environments where /proc is masked or virtualized; racy PID reuse checks in supervisory code.","solutions":["Retry the read: re-read /proc/<pid>/stat; a transient truncation usually resolves or turns into a clean ESRCH/'no longer running' condition","Check that the target PID still exists (os.Stat on /proc/<pid>) before parsing and treat failure as process-gone","Verify you are reading /proc/<pid>/stat (not statm/status) and that the read returned a full buffer","If running in a container, confirm /proc is the host or namespace proc you expect and not a masked path"],"exampleFix":"// before\ndata, _ := os.ReadFile(fmt.Sprintf(\"/proc/%d/stat\", pid))\nstart, err := parseStartTime(string(data))\n// after\ndata, err := os.ReadFile(fmt.Sprintf(\"/proc/%d/stat\", pid))\nif err != nil { return \"\", err } // PID gone -> treat as ESRCH\nif !strings.Contains(string(data), \")\") { return processStartTime(pid) } // retry once on truncation\nstart, err := parseStartTime(string(data))","handlingStrategy":"retry","validationCode":"if _, err := os.Stat(fmt.Sprintf(\"/proc/%d/stat\", pid)); err != nil { return ErrProcessGone }","typeGuard":"func isStatTruncated(err error) bool { return strings.Contains(err.Error(), \"missing comm terminator\") }","tryCatchPattern":"start, err := processStartTime(pid)\nif err != nil {\n    if isStatTruncated(err) || errors.Is(err, unix.ESRCH) { return retryOnce(pid) }\n    return err\n}","preventionTips":["Always re-read /proc/<pid>/stat fresh per call; never cache contents","Check process existence (os.Stat /proc/<pid>) before parsing","Expect races with short-lived processes and retry transient parse failures once","In containers, verify /proc is not masked before using procid"],"tags":["linux","procfs","process-management","race-condition"],"backgroundTag":"proc-stat-parse-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}