wavetermdev/waveterm · error
procinfo: read %s: %w
Error message
procinfo: read %s: %w
What it means
os.ReadFile of /proc/<pid>/stat failed with an error other than ENOENT (which is mapped to ErrNotFound instead). This is a wrapped I/O error, typically EACCES or EIO. The procinfo package surfaces the underlying errno via %w so callers can errors.Is/errors.As it.
Source
Thrown at pkg/util/procinfo/procinfo_linux.go:60
} else if errors.Is(err, ErrNotFound) {
return nil, ErrNotFound
}
return info, nil
}
// readStat parses /proc/[pid]/stat.
//
// The comm field (field 2) is enclosed in parentheses and may contain spaces
// and even parentheses itself, so we locate the last ')' to find the field
// boundary rather than splitting on whitespace naively.
func readStat(pid int32) (*ProcInfo, error) {
path := fmt.Sprintf("/proc/%d/stat", pid)
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("procinfo: read %s: %w", path, err)
}
s := strings.TrimRight(string(data), "\n")
// Locate comm: everything between first '(' and last ')'.
lp := strings.Index(s, "(")
rp := strings.LastIndex(s, ")")
if lp < 0 || rp < 0 || rp <= lp {
return nil, fmt.Errorf("procinfo: malformed stat for pid %d", pid)
}
pidStr := strings.TrimSpace(s[:lp])
comm := s[lp+1 : rp]
rest := strings.Fields(s[rp+1:])
// rest[0] = field 3 (state), rest[1] = field 4 (ppid), ...
// Fields after comm are numbered starting at 3, so rest[i] = field (i+3).
// We need:
// rest[0] = field 3 stateView on GitHub (pinned to a4447c1563)
Solutions
- Check the wrapped errno with errors.Is(err, fs.ErrPermission); if EACCES, run as root or in the same user/proc group as the target
- On hidepid mounts, add the reader to the proc group (group= option of /proc mount) or mount /proc without hidepid
- If the error is ESRCH/ENOENT-like because the process died, treat it as process-not-found rather than fatal
- Confirm you are on real Linux with /proc mounted (procfs), not a stripped container
- Check LSM logs (dmesg / auditd) for SELinux/AppArmor denials and add allow rules
Example fix
// caller-side
got, err := procinfo.GetProcInfo(ctx, nil, pid)
var perr *fs.PathError
if err != nil && errors.As(err, &perr) && errors.Is(perr.Err, os.ErrPermission) {
// hidepid/permission issue: fall back to elevated reader
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go: ensure the process exists and is readable before calling
if _, err := os.Stat(fmt.Sprintf("/proc/%d/stat", pid)); err != nil {
// missing or unreadable; resolve permissions first
} Try / catch
info, err := procinfo.GetProcInfo(ctx, nil, pid)
var perr *fs.PathError
if err != nil {
if errors.As(err, &perr) && errors.Is(perr.Err, os.ErrPermission) {
// EACCES from hidepid/LSM: fall back to elevated reader or skip
}
return err
} Prevention
- Avoid hidepid=2 unless the reader is in the proc group
- Run monitoring agents under the same uid as monitored processes
- Unwrap with errors.Is/errors.As to distinguish permission vs other I/O failures
- Check audit logs for LSM denials in hardened environments
When it happens
Trigger: GetProcInfo on Linux when /proc/<pid>/stat exists but cannot be read: EACCES from hidepid=2/3 proc mounts, or the process exited mid-read producing an unusual error (not ENOENT).
Common situations: Systems mounting /proc with hidepid=1/2 and the caller not in the proc group; restricted containers (Docker without pid namespace access); SELinux/AppArmor denials; attempting to read a kernel-thread pid from a restricted context.
Related errors
- procinfo: process not found
- procinfo: malformed stat for pid %d
- procinfo: too few fields in stat for pid %d
- procinfo: parse pid: %w
- procinfo: Uid line not found in %s
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/72e66581c0606dde.
Report an issue: GitHub.