wavetermdev/waveterm · error

no controller found for block %s

Error message

no controller found for block %s

What it means

SendInput looks up a registered controller for the given blockId before forwarding input. If no controller is registered in the in-memory registry for that block (never started, already shut down, or process restarted), it returns 'no controller found for block %s'. Input cannot be delivered without a live controller.

Source

Thrown at pkg/blockcontroller/blockcontroller.go:327

	}

	connOpts, parseErr := remote.ParseOpts(connName)
	if parseErr != nil {
		return
	}
	sshConn := conncontroller.MaybeGetConn(connOpts)
	if sshConn != nil {
		monitor := sshConn.GetMonitor()
		if monitor != nil {
			monitor.NotifyInput()
		}
	}
}

func SendInput(blockId string, inputUnion *BlockInputUnion) error {
	controller := getController(blockId)
	if controller == nil {
		return fmt.Errorf("no controller found for block %s", blockId)
	}
	sendConnMonitorInputNotification(controller)
	return controller.SendInput(inputUnion)
}

// only call this on shutdown
func StopAllBlockControllersForShutdown() {
	controllers := getAllControllers()
	for blockId, controller := range controllers {
		status := controller.GetRuntimeStatus()
		if status != nil && status.ShellProcStatus == Status_Running {
			go func(id string, c Controller) {
				c.Stop(true, Status_Done, false)
				wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, id))
			}(blockId, controller)
		}
	}
}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Call ResyncController for the blockId first so the controller is created, then retry SendInput
  2. Check GetRuntimeStatus / controller registration before sending input
  3. Refresh the frontend block state so it stops sending input to stale blocks
  4. If this happens at startup, gate input events until block controllers are resynced

Example fix

// before
SendInput(blockId, input) // controller may not exist
// after
if err := ResyncController(ctx, tabId, blockId, rtOpts, false); err == nil {
    SendInput(blockId, input)
}
Defensive patterns

Strategy: validation

Validate before calling

func controllerReady(blockId string) bool { return getController(blockId) != nil }

Try / catch

if err := SendInput(blockId, input); err != nil && strings.Contains(err.Error(), "no controller found") {
    // resync then retry
}

Prevention

When it happens

Trigger: Calling SendInput (via ControllerInputCommand) for a blockId whose controller was never created via ResyncController, or whose controller was unregistered on shutdown/cleanup.

Common situations: Typing into a terminal block after the backend restarted while the frontend still shows the old block; sending input to a block before its controller finished starting; racing a block close with input events.

Related errors


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