wavetermdev/waveterm · error
error writing to pty: %w
Error message
error writing to pty: %w
What it means
After successfully decoding InputData64, HandleInput writes the bytes to the job's controlling PTY (jm.cmdPty). This error wraps any failure from that pty Write, which means the job process's terminal is no longer writable (process exited and pty closed, or an OS-level I/O error on the pty master).
Source
Thrown at pkg/jobmanager/jobcmd.go:196
// TODO set up a single input handler loop + queue so we dont need to hold the lock but still get synchronized in-order execution
func (jm *JobCmd) HandleInput(data wshrpc.CommandJobInputData) error {
jm.lock.Lock()
defer jm.lock.Unlock()
if jm.cmd == nil || jm.cmdPty == nil {
return fmt.Errorf("no active process")
}
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
}View on GitHub (pinned to a4447c1563)
Solutions
- Check the job's exit status before sending input; treat this error as 'job already finished' and stop writing
- Retry once after confirming the job process is still alive (jm.Cmd.ProcessState / exit check)
- Buffer input client-side and drop pending input when a job-exit event is received
- If it happens immediately at start, inspect MakeJobCmd/PTY setup and OS logs for pty allocation failures
Example fix
// before
err := jm.cmdPty.Write(inputBuf[:nw])
// after
if jm.Cmd == nil || jm.Cmd.Process == nil || jm.Cmd.ProcessState != nil {
return fmt.Errorf("job not running, skipping pty write")
}
_, err := jm.cmdPty.Write(inputBuf[:nw]) Defensive patterns
Strategy: try-catch
Validate before calling
func jobWritable(jm *jobmanager.JobManager) bool {
return jm != nil && jm.Cmd != nil && jm.Cmd.Process != nil && jm.Cmd.ProcessState == nil
} Try / catch
if err := jm.HandleInput(data); err != nil {
if strings.Contains(err.Error(), "error writing to pty") {
// treat as job-finished: mark job ended, discard queued input
return
}
return err
} Prevention
- Stop sending input when the job-exit event arrives
- Drain/drop pending input queues on process exit
- Check ProcessState before writing
- Retry at most once with an aliveness check
When it happens
Trigger: Sending job input after the job process has already exited and the PTY has been closed; writing to a pty whose master fd hit an EIO because the slave side has no reader; a race between job termination and a queued input write.
Common situations: User types into a still-open terminal pane after the shell command finished; a script sends input with a delay while the process already consumed EOF and exited; job killed by a signal moments before input arrives.
Related errors
- error reading input: %w
- error writing osc message (AdaptMsgChToPty): %w
- reading from stdin: %w
- reading file %s: %w
- failed to read JWT token from stdin: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/4a76030e90c7af6f.
Report an issue: GitHub.