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

  1. Wait for the shell/terminal 'running' state before sending input.
  2. Restart the block (Start) if the shell exited, then send input.
  3. Check process state via the block controller status before writing.
  4. 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

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


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