{"record":{"id":"1148337d94962e0a","repo":"wavetermdev/waveterm","slug":"procinfo-malformed-stat-for-pid-d","errorCode":null,"errorMessage":"procinfo: malformed stat for pid %d","messagePattern":"procinfo: malformed stat for pid (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/util/procinfo/procinfo_linux.go","lineNumber":68,"sourceCode":"// The comm field (field 2) is enclosed in parentheses and may contain spaces\n// and even parentheses itself, so we locate the last ')' to find the field\n// boundary rather than splitting on whitespace naively.\nfunc readStat(pid int32) (*ProcInfo, error) {\n\tpath := fmt.Sprintf(\"/proc/%d/stat\", pid)\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn nil, ErrNotFound\n\t\t}\n\t\treturn nil, fmt.Errorf(\"procinfo: read %s: %w\", path, err)\n\t}\n\ts := strings.TrimRight(string(data), \"\\n\")\n\n\t// Locate comm: everything between first '(' and last ')'.\n\tlp := strings.Index(s, \"(\")\n\trp := strings.LastIndex(s, \")\")\n\tif lp < 0 || rp < 0 || rp <= lp {\n\t\treturn nil, fmt.Errorf(\"procinfo: malformed stat for pid %d\", pid)\n\t}\n\n\tpidStr := strings.TrimSpace(s[:lp])\n\tcomm := s[lp+1 : rp]\n\trest := strings.Fields(s[rp+1:])\n\n\t// rest[0] = field 3 (state), rest[1] = field 4 (ppid), ...\n\t// Fields after comm are numbered starting at 3, so rest[i] = field (i+3).\n\t// We need:\n\t//   rest[0]  = field  3  state\n\t//   rest[1]  = field  4  ppid\n\t//   rest[11] = field 14  utime\n\t//   rest[12] = field 15  stime\n\t//   rest[17] = field 20  num_threads\n\t//   rest[21] = field 24  rss (pages)\n\tif len(rest) < 22 {\n\t\treturn nil, fmt.Errorf(\"procinfo: too few fields in stat for pid %d\", pid)\n\t}","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/util/procinfo/procinfo_linux.go#L50-L86","documentation":"The library parsed /proc/<pid>/stat and could not locate a valid comm field: there must be text of the form 'pid (comm) rest...'. It finds the first '(' and last ')'; if either is missing or the ')' does not come after '(', the stat content is considered malformed. This indicates the file did not contain an expected procfs stat record.","triggerScenarios":"Reading a truncated or empty stat file (racing process exit can yield partial content in some kernels/filesystems); the path resolved to something that is not a proc stat file (e.g. procfs not mounted, an overlay exposing a different file); corrupted data from unusual virtualization layers.","commonSituations":"Flaky reads of a dying process's stat in a process monitor loop; misconfigured containers where /proc is not procfs (e.g. bind-mounted to a regular dir); testing with fake /proc fixtures that lack parentheses.","solutions":["Retry the read — a racing exit usually resolves to ENOENT or a fresh valid read on the next attempt","Verify /proc/<pid>/stat actually contains a stat line (cat it manually) and that /proc is procfs (mount | grep proc)","Treat the pid as gone and skip it in monitoring loops","Check for fixtures/mocks if the failure happens in tests — a stub file without '(comm)' triggers this"],"exampleFix":"// before\nlp := strings.Index(s, \"(\")\nrp := strings.LastIndex(s, \")\")\nif lp < 0 || rp < 0 || rp <= lp {\n    return nil, fmt.Errorf(\"procinfo: malformed stat for pid %d\", pid)\n}\n// after: tolerate empty/truncated reads as transient\nlp := strings.Index(s, \"(\")\nrp := strings.LastIndex(s, \")\")\nif lp < 0 || rp < 0 || rp <= lp {\n    if data == nil || len(data) == 0 {\n        return nil, ErrNotFound // likely raced exit\n    }\n    return nil, fmt.Errorf(\"procinfo: malformed stat for pid %d: %q\", pid, s)\n}","handlingStrategy":"retry","validationCode":"// pre-validate the stat line shape before parsing pipelines that depend on it\ndata, err := os.ReadFile(fmt.Sprintf(\"/proc/%d/stat\", pid))\nif err == nil && !bytes.Contains(data, []byte(\"(\")) {\n    // malformed/transient; wait and re-read\n}","typeGuard":null,"tryCatchPattern":"_, err := procinfo.GetProcInfo(ctx, nil, pid)\nif err != nil && strings.Contains(err.Error(), \"malformed stat\") {\n    // likely transient (racing exit): retry once, then treat pid as gone\n    time.Sleep(10 * time.Millisecond)\n    info, err = procinfo.GetProcInfo(ctx, nil, pid)\n}","preventionTips":["Treat malformed stat as a transient condition in monitoring loops","Ensure /proc is real procfs in containers (not a bind-mounted dir)","Validate test fixtures contain a full 'pid (comm) ...' line","Skip-and-log rather than crash when a pid vanishes mid-poll"],"tags":["linux","procfs","parsing","process-info"],"backgroundTag":"proc-stat-malformed","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}