Hmbown/CodeWhale · error · ExecError

screen recorder produced no video

Error message

screen recorder produced no video

What it means

recordingStop verifies that the output video file exists and is non-empty after the recorder exits cleanly; if the file is missing or zero bytes, the recording produced no usable video and the library throws instead of returning an empty result.

Solutions

  1. Ensure some real duration between recording start and stop (record at least a few seconds).
  2. Check disk space and that the output directory is writable.
  3. Verify the intended display/window exists at start time (switch_display/list_displays).
  4. Retry the recording; if persistent, run the recorder command manually to inspect its behavior with zero-length output.

Example fix

// before
await backend.recordingStart({}); await backend.recordingStop({ id });
// after
await backend.recordingStart({});
await new Promise(r => setTimeout(r, 3000));
const out = await backend.recordingStop({ id });
Defensive patterns

Strategy: retry

Validate before calling

null

Try / catch

try {
  const out = await backend.recordingStop({ id });
} catch (e) {
  if (e.message === 'screen recorder produced no video') {
    // discard, then re-record with a guaranteed minimum duration
  } else throw e;
}

Prevention

When it happens

Trigger: Stopping a recording where the recorder exited with code 0 but wrote nothing — e.g. recording started and stopped almost instantly, a race where the capture never began, or the output path was unwritable/cleaned up before stat.

Common situations: Start/stop called back-to-back in a script with no capture duration; a display with no content selected; disk-full or sandboxed temp directory silently swallowing output; a recorder upgrade changing output semantics.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:692

    } 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;
    await browser.close().catch(() => {});
    const owned = [...rec.entries()];
    for (const [, recording] of owned) requestRecordingStop(recording);
    const results = await Promise.all(owned.map(async ([id, recording]) => {
      const result = await waitForRecordingStop(recording, 2_000);

View on GitHub (pinned to 73e0f67d83)