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

  1. Wait for the cleanup to finish (poll controlStatus / await the control-channel acknowledgement) and resend "ready".
  2. Add a short retry with backoff when receiving this message.
  3. Have the supervisor wait for the "ready" transition to be confirmed before issuing further commands.
  4. 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

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


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)