Hmbown/CodeWhale · error · ExecError

unknown recording

Error message

unknown recording "${id}"

What it means

recordingStop on win32 always fails because recordingStart on this platform throws (recording is unavailable until the recorder has session-owned cleanup), so no recording handle can ever exist. Stopping a recording with any id therefore throws 'unknown recording' — the id cannot match a live recording. recordingStatus always reports running:false.

Solutions

  1. Do not call recordingStart/recordingStop on win32 — use screenshot actions instead, as the recordingStart error message advises.
  2. Guard cleanup code: only call recordingStop if recordingStart succeeded (track the returned id).
  3. Use recordingStatus({ id }) (it never throws) to check running:false before attempting a stop.
  4. If screen recording is required, run on a platform whose backend supports session-owned recorder cleanup.

Example fix

// before
const rec = await backend.recordingStart({});
try { /* work */ } finally { await backend.recordingStop({ id: rec.id }); }
// after
let rec = null;
try { rec = await backend.recordingStart({}).catch(() => null); } catch {}
try { /* work */ } finally { if (rec) await backend.recordingStop({ id: rec.id }); }
Defensive patterns

Strategy: type-guard

Type guard

const canRecord = (backend) => backend.backendId !== 'win32';

Try / catch

try {
  await backend.recordingStop({ id });
} catch (e) {
  if (/unknown recording/.test(e.message)) return { stopped: false, reason: 'recording unsupported on this backend' };
  throw e;
}

Prevention

When it happens

Trigger: Calling recordingStop({ id }) on the win32 backend with any id value — a previously started-looking id, a stale id from another backend, or a fabricated one. Unconditional, since no recording can ever start on this backend.

Common situations: Agents replaying a start/stop recording workflow ported from the macOS/Linux backend; cleanup handlers blindly calling recordingStop in a finally block; ids persisted from an earlier session.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/00dfef3da2241dfb. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:579

      return { text: j.text ?? "", encoding: "utf8" };
    },
    write_clipboard: async ({ text }) => {
      const b64 = Buffer.from(String(text ?? ""), "utf16le").toString("base64");
      await psOk(`Set-Clipboard -Value ([System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${b64}')));
Write-Output '{"ok": true}';`, { timeoutMs: 10_000 });
      return { written: String(text ?? "").length };
    },
    cursor_position: async () => {
      const j = await psJson(`${USER32_PRELUDE}
$p = New-Object User32+POINT;
[void][User32]::GetCursorPos([ref]$p);
Write-Output ('{"x": ' + $p.X + ', "y": ' + $p.Y + '}');`);
      return { x: j.x, y: j.y };
    },
    recordingStart: async () => {
      throw Object.assign(new ExecError("Recording is unavailable on this platform until the recorder has session-owned cleanup. Use screenshots instead."), { code: "owned_recording_unavailable" });
    },
    recordingStop: async ({ id }) => { throw new ExecError(`unknown recording "${id}"`); },
    recordingStatus: ({ id }) => ({ id, running: false }),
    recordingList: async () => {
      const dir = recordingsDir();
      const out = fs.existsSync(dir)
        ? fs.readdirSync(dir).filter((f) => /\.mp4$/i.test(f)).map((f) => {
            const st = fs.statSync(path.join(dir, f));
            return { file: path.join(dir, f), bytes: st.size, modifiedAt: st.mtime.toISOString() };
          }).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, 50)
        : [];
      return { dir, recordings: out, running: [] };
    },
  };

  async function clickAt(button, x, y, clicks) {
    if (!Number.isFinite(Number(x)) || !Number.isFinite(Number(y))) throw new ExecError("coordinates must be finite numbers");
    const flags = button === 1 ? "RIGHTDOWN, RIGHTUP" : button === 2 ? "MIDDLEDOWN, MIDDLEUP" : "LEFTDOWN, LEFTUP";
    const seq = [];
    for (let i = 0; i < clicks; i++) seq.push(`[User32]::mouse_event([User32]::${flags.split(",")[0].trim()}, 0, 0, 0, [UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [User32]::mouse_event([User32]::${flags.split(",")[1].trim()}, 0, 0, 0, [UIntPtr]::Zero); Start-Sleep -Milliseconds 60;`);

View on GitHub (pinned to 73e0f67d83)