Hmbown/CodeWhale · warning · Error
Input is still being released; try again in a moment.
Error message
Input is still being released; try again in a moment.
What it means
When switching the control mode to "ready", `setControlMode` first checks whether a previous cleanup (release of injected input, gesture abort) is still in progress (`cleanupPending`). If so it refuses with "Input is still being released; try again in a moment." This ordering guarantees even a held synthetic gesture yields to the human before input control is handed back.
Solutions
- Wait for the cleanup to finish (poll controlStatus / await the control-channel acknowledgement) and resend "ready".
- Add a short retry with backoff when receiving this message.
- Have the supervisor wait for the "ready" transition to be confirmed before issuing further commands.
- Investigate why cleanup is slow if the condition persists (stuck gesture release).
Example fix
// before\nawait setControlMode("stopped");\nawait setControlMode("ready"); // throws if cleanup pending\n// after\nawait setControlMode("stopped");\nawait waitForControlStatus(s => !s.cleanupPending);\nawait setControlMode("ready"); Defensive patterns
Strategy: retry
Validate before calling
const st = await controlStatus();\nif (st.cleanupPending) await sleep(100); // before requesting ready
Try / catch
try { await setControlMode("ready"); } catch (e) { if (/still being released/.test(e.message)) { await sleep(200); return setControlMode("ready"); } throw e; } Prevention
- Wait for the mode transition to be acknowledged before issuing the next command.
- Never fire paused/stopped/ready transitions back-to-back without awaiting.
- Add bounded backoff retry around ready transitions.
When it happens
Trigger: Sending mode "ready" while a prior "stopped" transition is still running its cleanup: e.g. a supervisor sends paused→stopped→ready in rapid succession, or a launcher auto-restarts and immediately re-enables input while the previous teardown is mid-flight.
Common situations: Fast restart loops, watchdog scripts that toggle modes without waiting for the ready acknowledgement, or a user interrupting input injection and immediately retrying.
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
- Agent Mail can be canceled only while queued
- Agent Mail can be marked read only after delivery
- app_denied
- {backoff.last_error}
- click supports left with 1-3 clicks, right x1 or middle x1
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/b1fd9fd5db5568c6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/app-handler.mjs:63
const sessions = new Map();
let controlMode = "ready";
let controlGeneration = 0;
let cleanupPending = false;
/** Human-facing state contains app identity and action names, never task text. */
export function controlStatus() {
return { mode: controlMode, cleanupPending, sessions: [...sessions.values()]
.filter((s) => !s.closed && s.target)
.map((s) => ({ target: s.target, mode: s.mode, action: s.action ?? null })) };
}
// Called only by the launcher's inherited control channel, never an MCP tool.
// Abort before queuing cleanup so even a held gesture yields to the person.
export async function setControlMode(mode) {
if (!["ready", "paused", "stopped"].includes(mode)) throw new Error("Unknown control mode");
if (mode === "ready") {
if (cleanupPending) throw new Error("Input is still being released; try again in a moment.");
controlMode = mode;
return controlStatus();
}
controlMode = mode;
const generation = ++controlGeneration;
cleanupPending = true;
const results = await Promise.allSettled([...sessions.keys()].map((key) => {
const colon = key.indexOf(":");
return releaseSessionInput(key.slice(colon + 1), key.slice(0, colon), { close: mode === "stopped" });
}));
const failure = results.find((r) => r.status === "rejected");
// A cleanup failure stays blocked. Resume cannot hide owned input.
if (failure) throw failure.reason;
if (generation === controlGeneration) cleanupPending = false;
return controlStatus();
}
function enqueue(fn) {View on GitHub (pinned to 73e0f67d83)