Hmbown/CodeWhale · error · ExecError
unknown or already-finished recording
Error message
unknown or already-finished recording "${id}" What it means
recordingStop({id}) looks up the recording handle in an in-memory map; this error means the id is not present, so it was never started by this session or its stop already completed (the map entry is deleted on successful stop). The library throws rather than guessing which stale or fabricated id you meant.
Solutions
- Check the id against the value returned by the recording start call in the CURRENT session.
- If the stop may have already succeeded, treat the recording as stopped and read the file from the previously returned result instead of stopping again.
- If the session restarted, the recording is unmanageable via this API; re-start a new recording if one is still needed.
- Catch the error and call the recording status/inspect surface to confirm no live recording with that id exists before retrying.
Example fix
// before
await backend.recordingStop({ id: rememberedIdFromOldSession });
// after
if (sessionRecording && sessionRecording.id === id) {
const out = await backend.recordingStop({ id });
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!currentRecording || currentRecording.id !== id) {
throw new Error(`no live recording id ${id} in this session`);
} Try / catch
try {
const out = await backend.recordingStop({ id });
} catch (e) {
if (String(e.message).includes('unknown or already-finished recording')) {
// already stopped or stale id — treat as no-op, use retained file
} else throw e;
} Prevention
- Keep the recording id from the start call in session-scoped state only.
- Treat stop as idempotent-by-catch: a second stop should be a no-op, not a retry.
- Never persist recording ids across plugin/session restarts.
When it happens
Trigger: Calling screen-recording stop with an id returned by a previous session, an id already stopped successfully (the entry is deleted from the rec map at the end of recordingStop), a typo'd id, or after the plugin/session was restarted and its in-memory recording registry was cleared.
Common situations: An agent retries a stop call after a first stop already succeeded; the TUI restarted between recording start and stop so all ids became invalid; the id was hallucinated or copied from a log of an old 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
- choose one available display for recording
- durationSec must be positive
- ffmpeg is required on the host to mux harmony…
- ffmpeg mux failed
- Harmony recording closed during startup
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8bc8544984fb556e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:687
completion.then(result => { if (!ready) reject(new ExecError(result.error || "screen recorder exited before capture started")); });
});
throwIfAborted();
return { id, pid: child.pid, file, display: disp, durationSec: durationSec ?? null, region: region ?? null, fps: 30, mode: "ScreenCaptureKit", startedAt,
...(window ? { window: { id: window.window_id ?? null, name: window.name ?? null }, note: "Recording the window's rect as it was at start; it does not track moves or resizes." } : {}) };
} catch (error) {
requestRecordingStop(recording);
const result = await waitForRecordingStop(recording, 2_000);
if (result.terminated !== false) rec.delete(id);
throw error;
} finally {
clearTimeout(timer);
signal?.removeEventListener("abort", abort);
}
}
async function recordingStop({ id }) {
const r = rec.get(id);
if (!r) throw new ExecError(`unknown or already-finished recording "${id}"`);
requestRecordingStop(r);
const result = await waitForRecordingStop(r, 20_000);
if (result.code !== 0) throw new ExecError(result.error || "screen recorder failed; partial file retained");
const size = fs.existsSync(r.file) ? fs.statSync(r.file).size : 0;
if (!size) throw new ExecError("screen recorder produced no video");
rec.delete(id);
return { id, file: r.file, mp4: null, bytes: size, mode: r.mode, startedAt: r.startedAt, stoppedAt: new Date().toISOString() };
}
async function closeSession() {
// The preview this session showed must not outlive the session; a panel
// from a dead session has no owner to refresh or hide it.
stopPreviewLoop();
await quiescePreview();
if (state.previewEnabled && state.inputApp) {
try { await native("preview_notify", { enabled: false }); } catch { /* hiding is best-effort */ }
}
state.previewEnabled = false;View on GitHub (pinned to 73e0f67d83)