Hmbown/CodeWhale · error · ServerError
control_stopped
control_stopped
Error message
stop_computer_control is active; no further actions are permitted this session
What it means
stop_computer_control is a session kill switch. Once active, the dispatch loop re-checks it before every tool call and refuses any non-read-only tool with control_stopped, including actions already mid-preparation when stop arrived. Read-only tools (e.g. screenshot, get_app_state) remain allowed by READ_ONLY_TOOLS.
Solutions
- Re-initialize or restart the computer-control session to clear the kill switch if further actions are genuinely needed
- Remove subsequent mutating steps after stop_computer_control in any queued run_actions batch
- Restrict post-stop work to read-only tools (screenshot, get_app_state) that READ_ONLY_TOOLS permits
Example fix
// before
await stopComputerControl();
await click({ target: el }); // throws control_stopped
// after
await stopComputerControl();
// only read-only calls afterwards, or start a new session before mutating calls Defensive patterns
Strategy: try-catch
Validate before calling
if (controlStopped && !READ_ONLY_TOOLS.has(toolName)) throw new Error("session control stopped; refusing " + toolName); Type guard
const isReadOnlyTool = (name) => READ_ONLY_TOOLS.has(name);
Try / catch
try { await callTool(name, args) } catch (e) { if (e.code === "control_stopped") { /* stop the queue; do not retry mutating calls; restart session if actions are still required */ } else throw e; } Prevention
- Drain or discard queued actions when stop_computer_control is issued
- Track a local stopped flag mirroring the server's kill switch
- Only schedule read-only work after a stop
When it happens
Trigger: Any mutating computer-use call (click, type, key, run_actions, app_script, ...) issued after stop_computer_control was called in the session; a stop racing with an in-flight prepareArgs resolves first and still blocks dispatch.
Common situations: Automations that finished their stop but retried; a user pressing a stop/emergency control then a queued step firing; long-lived sessions where an earlier stop is forgotten.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/e1023e8d3186226a.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:957
ex.closeChannel?.();
return ex.remote(request, opts);
}
throw err;
}
}
return ex.remote(request, opts);
};
const resolve = async (req) => {
const rep = await remoteCall({ tool: "resolve_element", args: req }, { timeoutMs: 30_000 });
if (!rep?.ok) return { found: false, element: null, reason: rep?.error?.code ?? "remote_error" };
return rep.data;
};
const wireArgs = await prepareArgs(computer, name, args, resolve, sink);
throwIfAborted();
await assertCurrentRoute(computer, binding);
// Re-check the kill switch: a stop that arrived while the executor was
// being resolved still blocks this dispatch.
if (controlStopped && !READ_ONLY_TOOLS.has(name)) throw new ServerError("control_stopped", "stop_computer_control is active; no further actions are permitted this session");
inFlight++;
try {
dispatched = true;
const timeoutMs = backendMethod.startsWith("recording") || backendMethod === "get_app_state" ? 60_000 : 30_000;
const invoke = async (tool, a) => {
const r = await remoteCall({ tool, args: a }, { timeoutMs });
if (!r.ok) throw new ServerError(r.error?.code ?? "remote_error", r.error?.message ?? "remote agent failed");
return r.data;
};
data = name === "type" ? await invokeType(invoke, wireArgs) : await invoke(backendMethod, wireArgs);
} finally {
inFlight--;
}
await assertCurrentRoute(computer, binding, true);
if (Array.isArray(data)) data = { items: data };
if ((backendMethod === "screenshot" || backendMethod === "zoom") && data?.file) {
if (ex.filesLocal) bindRaster(computer, data);
else {View on GitHub (pinned to 73e0f67d83)