{"record":{"id":"bdd72f294e4f49af","repo":"gastownhall/beads","slug":"procid-malformed-proc-stat-missing-starttime","errorCode":null,"errorMessage":"procid: malformed proc stat: missing starttime","messagePattern":"procid: malformed proc stat: missing starttime","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/procid/procid_linux.go","lineNumber":209,"sourceCode":"\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\n\terr  error\n}\n\nfunc (e *bootIDReadError) Error() string {\n\treturn fmt.Sprintf(\"procid: read boot ID %s: %v\", e.path, e.err)\n}","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/procid/procid_linux.go#L191-L227","documentation":"parseStartTime splits everything after the comm terminator ')' into fields. The remainder starts with state (field 3 of stat), so starttime (field 22) is the 20th token. This error means the post-comm portion had fewer than 20 fields, i.e. the stat line was truncated before starttime.","triggerScenarios":"Calling processStartTime on a process that exited or is being reaped while /proc/<pid>/stat is read, yielding a partial line; reading a stat file whose content was cut short by a concurrent kernel update.","commonSituations":"Bulk PID scans over /proc racing with process churn; test suites spawning and killing processes rapidly; filesystem snapshots or overlays exposing partial procfs content.","solutions":["Retry the read once — truncation is almost always a transient exit race","Treat persistent failure as process-gone (the parser already maps Z/X/x states to ESRCH) and fall back to a liveness check via signal 0 or os.Stat on /proc/<pid>","Log the raw stat content to confirm it is genuinely truncated rather than a parser bug","Avoid caching stat contents; always re-read freshly per call"],"exampleFix":"// before\nstart, err := processStartTime(pid) // fails with missing starttime on racing exit\nif err != nil { return err }\n// after\nstart, err := processStartTime(pid)\nif err != nil {\n    if errors.Is(err, unix.ESRCH) || strings.Contains(err.Error(), \"malformed proc stat\") {\n        return ErrProcessGone // treat as exited, retry discovery\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":"data, err := os.ReadFile(fmt.Sprintf(\"/proc/%d/stat\", pid)); if err == nil && strings.Count(string(data), \" \") >= 21 { /* likely complete */ }","typeGuard":"func isMissingStarttime(err error) bool { return strings.Contains(err.Error(), \"missing starttime\") }","tryCatchPattern":"start, err := processStartTime(pid)\nif err != nil {\n    if isMissingStarttime(err) { return ErrProcessGone } // exit race: treat as gone\n    return err\n}","preventionTips":["Treat parse failures on /proc stat as potential process-exit races","Fall back to a liveness check (signal 0 / os.Stat) on parse failure","Avoid bulk scans that hold stale PID lists while processes churn","Log the raw stat line once to distinguish truncation from parser bugs"],"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"}