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
- Ignore 'os: process already finished' errors — the desired outcome (process stopped) already happened
- Check jm.cmd.ProcessState is nil before signaling, and skip if the process has exited
- Use a done-channel/WaitGroup so signals are only sent while the job is live
- 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
- Track a done channel from cmd.Wait and gate signals on it
- Ignore 'process already finished' errors on stop paths
- Debounce duplicate stop/kill requests in the UI
- Guard signal sends behind the same lock the waiter uses
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
- window ${windowId} not found
- encryption is not available
- procinfo: process not found
- error starting controller: %w
- failed to start durable shell: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/0dd35abb2458e5ab.
Report an issue: GitHub.