{"record":{"id":"87b744f944744b8b","repo":"wavetermdev/waveterm","slug":"no-controller-found-for-block-s","errorCode":null,"errorMessage":"no controller found for block %s","messagePattern":"no controller found for block (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/blockcontroller/blockcontroller.go","lineNumber":327,"sourceCode":"\t}\n\n\tconnOpts, parseErr := remote.ParseOpts(connName)\n\tif parseErr != nil {\n\t\treturn\n\t}\n\tsshConn := conncontroller.MaybeGetConn(connOpts)\n\tif sshConn != nil {\n\t\tmonitor := sshConn.GetMonitor()\n\t\tif monitor != nil {\n\t\t\tmonitor.NotifyInput()\n\t\t}\n\t}\n}\n\nfunc SendInput(blockId string, inputUnion *BlockInputUnion) error {\n\tcontroller := getController(blockId)\n\tif controller == nil {\n\t\treturn fmt.Errorf(\"no controller found for block %s\", blockId)\n\t}\n\tsendConnMonitorInputNotification(controller)\n\treturn controller.SendInput(inputUnion)\n}\n\n// only call this on shutdown\nfunc StopAllBlockControllersForShutdown() {\n\tcontrollers := getAllControllers()\n\tfor blockId, controller := range controllers {\n\t\tstatus := controller.GetRuntimeStatus()\n\t\tif status != nil && status.ShellProcStatus == Status_Running {\n\t\t\tgo func(id string, c Controller) {\n\t\t\t\tc.Stop(true, Status_Done, false)\n\t\t\t\twstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, id))\n\t\t\t}(blockId, controller)\n\t\t}\n\t}\n}","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/blockcontroller/blockcontroller.go#L309-L345","documentation":"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.","triggerScenarios":"Calling SendInput (via ControllerInputCommand) for a blockId whose controller was never created via ResyncController, or whose controller was unregistered on shutdown/cleanup.","commonSituations":"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.","solutions":["Call ResyncController for the blockId first so the controller is created, then retry SendInput","Check GetRuntimeStatus / controller registration before sending input","Refresh the frontend block state so it stops sending input to stale blocks","If this happens at startup, gate input events until block controllers are resynced"],"exampleFix":"// before\nSendInput(blockId, input) // controller may not exist\n// after\nif err := ResyncController(ctx, tabId, blockId, rtOpts, false); err == nil {\n    SendInput(blockId, input)\n}","handlingStrategy":"validation","validationCode":"func controllerReady(blockId string) bool { return getController(blockId) != nil }","typeGuard":null,"tryCatchPattern":"if err := SendInput(blockId, input); err != nil && strings.Contains(err.Error(), \"no controller found\") {\n    // resync then retry\n}","preventionTips":["Resync controllers after backend restarts before accepting input","Drop queued input events for closed blocks","Gate keyboard handlers on controller runtime status"],"tags":["state","blockcontroller","input"],"backgroundTag":"controller-not-found","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}