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

  1. Unwrap to inspect the OS error (ESRCH vs EPERM) and handle each accordingly.
  2. Treat ESRCH as 'process exited' and fall back to GetExitInfo.
  3. Retry once immediately — the race window is tiny and usually resolves to a definitive exit.
  4. 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

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


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