wavetermdev/waveterm · warning

process already exited

Error message

process already exited

What it means

GetPGID tracks job termination via the processExited flag (set by the exit-waiter). Once the process has exited, its pgid is no longer meaningful/available, so the method refuses the lookup with this error.

Source

Thrown at pkg/jobmanager/jobcmd.go:125

	log.Printf("process exited: exitcode=%s, signal=%s, err=%v\n", exitCodeStr, jm.exitSignal, jm.exitErr)

	go WshCmdJobManager.sendJobExited()
}

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{

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check GetExitInfo() before calling GetPGID and skip if exited.
  2. Listen for the job-exited event instead of polling the pgid.
  3. Treat this error as a benign 'already done' condition in callers.
  4. Re-fetch fresh job state after restart if the pgid is genuinely needed.

Example fix

// before
pgid, err := jobCmd.GetPGID()
// after
exited, _ := jobCmd.GetExitInfo()
if exited {
    return nil // job already finished
}
pgid, err := jobCmd.GetPGID()
Defensive patterns

Strategy: validation

Validate before calling

exited, exitData := jobCmd.GetExitInfo()
if exited {
    return fmt.Errorf("job exited (code %v); pgid unavailable", exitData)
}

Type guard

func isRunning(jm *jobmanager.JobCmd) bool {
    exited, _ := jm.GetExitInfo()
    return !exited
}

Try / catch

pgid, err := jobCmd.GetPGID()
if err != nil {
    if err.Error() == "process already exited" {
        return nil // benign: treat as completed job
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetPGID after the job's process has terminated — e.g. querying the process group to send a signal but the command already finished (or was killed) and the exit watcher flipped processExited.

Common situations: Sending SIGTERM to a job that already completed; a UI still calling process-management RPCs on a finished block; race between process exit and a cleanup routine fetching the pgid.

Related errors


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