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  state

View on GitHub (pinned to a4447c1563)

Solutions

  1. 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
  2. On hidepid mounts, add the reader to the proc group (group= option of /proc mount) or mount /proc without hidepid
  3. If the error is ESRCH/ENOENT-like because the process died, treat it as process-not-found rather than fatal
  4. Confirm you are on real Linux with /proc mounted (procfs), not a stripped container
  5. 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

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


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/72e66581c0606dde. Report an issue: GitHub.