wavetermdev/waveterm · error
failed to get pgid: %w
Error message
failed to get pgid: %w
What it means
GetPGID resolves the process group via unixutil.GetProcessGroupId(jm.cmd.Process.Pid). If that syscall-level lookup fails (permission problem or the process vanished mid-call), the underlying error is wrapped as 'failed to get pgid'.
Source
Thrown at pkg/jobmanager/jobcmd.go:129
func (jm *JobCmd) GetCmd() (*exec.Cmd, pty.Pty) {
jm.lock.Lock()
defer jm.lock.Unlock()
return jm.cmd, jm.cmdPty
}
func (jm *JobCmd) GetPGID() (int, error) {
jm.lock.Lock()
defer jm.lock.Unlock()
if jm.cmd == nil || jm.cmd.Process == nil {
return 0, fmt.Errorf("no active process")
}
if jm.processExited {
return 0, fmt.Errorf("process already exited")
}
pgid, err := unixutil.GetProcessGroupId(jm.cmd.Process.Pid)
if err != nil {
return 0, fmt.Errorf("failed to get pgid: %w", err)
}
if pgid <= 0 {
return 0, fmt.Errorf("invalid pgid returned: %d", pgid)
}
return pgid, nil
}
func (jm *JobCmd) GetExitInfo() (bool, *wshrpc.CommandJobCmdExitedData) {
jm.lock.Lock()
defer jm.lock.Unlock()
if !jm.processExited {
return false, nil
}
exitData := &wshrpc.CommandJobCmdExitedData{
JobId: WshCmdJobManager.JobId,
ExitCode: jm.exitCode,
ExitSignal: jm.exitSignal,
ExitTs: jm.exitTs,View on GitHub (pinned to a4447c1563)
Solutions
- Unwrap to inspect the OS error (ESRCH vs EPERM) and handle each accordingly.
- Treat ESRCH as 'process exited' and fall back to GetExitInfo.
- Retry once immediately — the race window is tiny and usually resolves to a definitive exit.
- Fix environment permissions (hidepid / sandbox) if EPERM is the cause.
Example fix
// before
pgid, err := jobCmd.GetPGID()
// after
pgid, err := jobCmd.GetPGID()
if err != nil && errors.Is(err, unix.ESRCH) {
exited, _ := jobCmd.GetExitInfo()
_ = exited // process gone; handle as exited
} Defensive patterns
Strategy: retry
Validate before calling
exited, _ := jobCmd.GetExitInfo()
if exited {
return errors.New("skip pgid lookup: process exited")
} Type guard
func canLookupPGID(jm *jobmanager.JobCmd) bool {
exited, _ := jm.GetExitInfo()
return !exited
} Try / catch
pgid, err := jobCmd.GetPGID()
if err != nil && strings.Contains(err.Error(), "failed to get pgid") {
time.Sleep(10 * time.Millisecond)
pgid, err = jobCmd.GetPGID() // one retry; usually resolves the ESRCH race
if err != nil {
exited, _ := jobCmd.GetExitInfo()
if exited { return nil } // process gone; handle as exit
return err
}
} Prevention
- Expect races with fast-exiting processes and code a single retry
- Distinguish ESRCH (gone) from EPERM (permissions) after unwrapping
- Avoid hidepid / sandbox configs that block /proc metadata reads
When it happens
Trigger: The process exited between the processExited check and the GetProcessGroupId call (procfs/sysctl entry gone), or the caller lacks permission to read the other process's group.
Common situations: Tight races with fast-exiting processes; hardened /proc (hidepid) mount options; sandboxed environments restricting process metadata access.
Related errors
- no active process
- invalid pgid returned: %d
- failed to daemonize: %w
- failed to setsid: %w
- procinfo: process not found
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/d187c355e1ef587c.
Report an issue: GitHub.