{"record":{"id":"4a76030e90c7af6f","repo":"wavetermdev/waveterm","slug":"error-writing-to-pty-w","errorCode":null,"errorMessage":"error writing to pty: %w","messagePattern":"error writing to pty: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/jobmanager/jobcmd.go","lineNumber":196,"sourceCode":"\n// TODO set up a single input handler loop + queue so we dont need to hold the lock but still get synchronized in-order execution\nfunc (jm *JobCmd) HandleInput(data wshrpc.CommandJobInputData) error {\n\tjm.lock.Lock()\n\tdefer jm.lock.Unlock()\n\n\tif jm.cmd == nil || jm.cmdPty == nil {\n\t\treturn fmt.Errorf(\"no active process\")\n\t}\n\n\tif len(data.InputData64) > 0 {\n\t\tinputBuf := make([]byte, base64.StdEncoding.DecodedLen(len(data.InputData64)))\n\t\tnw, err := base64.StdEncoding.Decode(inputBuf, []byte(data.InputData64))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error decoding input data: %w\", err)\n\t\t}\n\t\t_, err = jm.cmdPty.Write(inputBuf[:nw])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error writing to pty: %w\", err)\n\t\t}\n\t}\n\n\tif data.SigName != \"\" {\n\t\tsig := unixutil.ParseSignal(data.SigName)\n\t\tif sig != nil && jm.cmd.Process != nil {\n\t\t\terr := jm.cmd.Process.Signal(sig)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error sending signal: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif data.TermSize != nil {\n\t\terr := jm.setTermSize_withlock(*data.TermSize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/jobmanager/jobcmd.go#L178-L214","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nerr := jm.cmdPty.Write(inputBuf[:nw])\n// after\nif jm.Cmd == nil || jm.Cmd.Process == nil || jm.Cmd.ProcessState != nil {\n    return fmt.Errorf(\"job not running, skipping pty write\")\n}\n_, err := jm.cmdPty.Write(inputBuf[:nw])","handlingStrategy":"try-catch","validationCode":"func jobWritable(jm *jobmanager.JobManager) bool {\n    return jm != nil && jm.Cmd != nil && jm.Cmd.Process != nil && jm.Cmd.ProcessState == nil\n}","typeGuard":null,"tryCatchPattern":"if err := jm.HandleInput(data); err != nil {\n    if strings.Contains(err.Error(), \"error writing to pty\") {\n        // treat as job-finished: mark job ended, discard queued input\n        return\n    }\n    return err\n}","preventionTips":["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"],"tags":["pty","io","process-lifecycle"],"backgroundTag":"broken-pipe-write-failure","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}