wavetermdev/waveterm · error
no shell input chan
Error message
no shell input chan
What it means
ShellController buffers terminal input through ShellInputCh, which is created during the shell startup sequence. SendInput refuses to send when that channel is nil, meaning the shell process has not started yet, already exited, or the channel was torn down. This prevents a blocking send (or panic) on a dead channel.
Source
Thrown at pkg/blockcontroller/shellcontroller.go:150
func (sc *ShellController) GetRuntimeStatus() *BlockControllerRuntimeStatus {
var rtn BlockControllerRuntimeStatus
sc.WithLock(func() {
rtn = sc.getRuntimeStatus_nolock()
})
return &rtn
}
func (sc *ShellController) GetConnName() string {
return sc.ConnName
}
func (sc *ShellController) SendInput(inputUnion *BlockInputUnion) error {
var shellInputCh chan *BlockInputUnion
sc.WithLock(func() {
shellInputCh = sc.ShellInputCh
})
if shellInputCh == nil {
return fmt.Errorf("no shell input chan")
}
shellInputCh <- inputUnion
return nil
}
func (sc *ShellController) WithLock(f func()) {
sc.Lock.Lock()
defer sc.Lock.Unlock()
f()
}
type RunShellOpts struct {
TermSize waveobj.TermSize `json:"termsize,omitempty"`
}
// only call when holding the lock
func (sc *ShellController) sendUpdate_nolock() {
rtStatus := sc.getRuntimeStatus_nolock()View on GitHub (pinned to a4447c1563)
Solutions
- Wait for the shell/terminal 'running' state before sending input.
- Restart the block (Start) if the shell exited, then send input.
- Check process state via the block controller status before writing.
- Add a readiness signal/guard in the calling UI around SendInput.
Example fix
// before
sc.SendInput(union) // no channel yet — error
// after
select {
case <-sc.WaitStarted(): // readiness signal from the controller
return sc.SendInput(union)
case <-time.After(5 * time.Second):
return fmt.Errorf("shell not ready")
} Defensive patterns
Strategy: type-guard
Validate before calling
var ch chan *BlockInputUnion
sc.WithLock(func() { ch = sc.ShellInputCh })
if ch == nil {
return fmt.Errorf("shell input not ready")
} Type guard
func (sc *ShellController) InputReady() bool {
var ok bool
sc.WithLock(func() { ok = sc.ShellInputCh != nil })
return ok
} Try / catch
if err := sc.SendInput(union); err != nil && err.Error() == "no shell input chan" {
// queue the input and flush once the shell reports running
pendingInput = append(pendingInput, union)
return nil
} Prevention
- Gate input UI on shell-running status, not block existence
- Rebuild the channel lifecycle on shell restart
- Flush queued input only after SendInput readiness is confirmed
When it happens
Trigger: Calling SendInput before Start has created the channel, after the shell process exits and the channel is closed/nil-ed, or when Start failed midway during setupAndStartShellProcess.
Common situations: User types into a terminal block while the shell is still starting or right after it exits; a crashed shell leaves the block present but input unattached; sending input from automation without waiting for readiness.
Related errors
- no job attached to controller
- no controller found for block %s
- error getting block: %w
- no vdom context block id
- client is done
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/980c2e3c10d79445.
Report an issue: GitHub.