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
- Do not call recordingStart/recordingStop on win32 — use screenshot actions instead, as the recordingStart error message advises.
- Guard cleanup code: only call recordingStop if recordingStart succeeded (track the returned id).
- Use recordingStatus({ id }) (it never throws) to check running:false before attempting a stop.
- 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
- Never call recordingStart/recordingStop on win32; use screenshots.
- Only stop recordings whose id came from a successful recordingStart in the same session.
- Use recordingStatus (non-throwing, always running:false on win32) to probe before stopping.
- Guard finally-block cleanup so it skips stop calls when start never succeeded.
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
- unknown recording
- unsupported_selector
- Agent continuation target is no longer retained
- Agent not found
- application window not found in UIA tree — pass…
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)