wavetermdev/waveterm · warning

error sending signal: %w

Error message

error sending signal: %w

What it means

HandleInput delivers a signal (SigName) to the job's process via cmd.Process.Signal. This error wraps the failure of that syscall, typically 'os: process already finished' since the job exited before the signal was sent.

Source

Thrown at pkg/jobmanager/jobcmd.go:205

	if len(data.InputData64) > 0 {
		inputBuf := make([]byte, base64.StdEncoding.DecodedLen(len(data.InputData64)))
		nw, err := base64.StdEncoding.Decode(inputBuf, []byte(data.InputData64))
		if err != nil {
			return fmt.Errorf("error decoding input data: %w", err)
		}
		_, err = jm.cmdPty.Write(inputBuf[:nw])
		if err != nil {
			return fmt.Errorf("error writing to pty: %w", err)
		}
	}

	if data.SigName != "" {
		sig := unixutil.ParseSignal(data.SigName)
		if sig != nil && jm.cmd.Process != nil {
			err := jm.cmd.Process.Signal(sig)
			if err != nil {
				return fmt.Errorf("error sending signal: %w", err)
			}
		}
	}

	if data.TermSize != nil {
		err := jm.setTermSize_withlock(*data.TermSize)
		if err != nil {
			return err
		}
	}

	return nil
}

func (jm *JobCmd) TerminateByClosingPtyMaster() {
	jm.lock.Lock()
	defer jm.lock.Unlock()
	if jm.ptyClosed {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Ignore 'os: process already finished' errors — the desired outcome (process stopped) already happened
  2. Check jm.cmd.ProcessState is nil before signaling, and skip if the process has exited
  3. Use a done-channel/WaitGroup so signals are only sent while the job is live
  4. If the signal is rejected on another platform, guard signal-sending behind a GOOS check

Example fix

// before
err := jm.cmd.Process.Signal(sig)
if err != nil {
    return fmt.Errorf("error sending signal: %w", err)
}
// after
if jm.cmd.ProcessState != nil {
    return nil // process already exited
}
err := jm.cmd.Process.Signal(sig)
if err != nil && !strings.Contains(err.Error(), "process already finished") {
    return fmt.Errorf("error sending signal: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if jm.Cmd == nil || jm.Cmd.Process == nil || jm.Cmd.ProcessState != nil {
    return // cannot signal a finished/nonexistent process
}

Try / catch

if err := jm.HandleInput(sigData); err != nil {
    var oe *os.PathError
    if errors.As(err, &oe) && strings.Contains(err.Error(), "process already finished") {
        return nil // benign race; job already exited
    }
    return err
}

Prevention

When it happens

Trigger: Sending a kill/interrupt signal to a job that has already exited; the process becoming a zombie or being reaped before Signal is called; platform rejecting the signal number.

Common situations: User clicks 'stop job' twice; a timeout fires a kill at the same moment the process exits normally; SIGWINCH/resize-signal arrives after job completion.

Related errors


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