Hmbown/CodeWhale · error · ExecError

screen recorder failed; partial file retained

Error message

screen recorder failed; partial file retained

What it means

After the stop is requested, recordingStop waits up to 20s for the underlying macOS screen-recorder child process to exit; if the child exits with a nonzero code the recorder failed, and the library throws this error while deliberately keeping the partial file on disk. The recorder's own stderr text (result.error) is preferred in the message when available.

Solutions

  1. Read the thrown message / result.error for the recorder's specific stderr and fix that root cause first.
  2. Verify Screen Recording permission is still granted to the host app (System Settings > Privacy & Security > Screen Recording).
  3. Inspect the retained partial file at the path from the start call — it may still be usable.
  4. Re-start the recording and retry; if it recurs, run the recorder command manually to see the raw failure.

Example fix

null
Defensive patterns

Strategy: fallback

Validate before calling

null

Try / catch

try {
  const out = await backend.recordingStop({ id });
} catch (e) {
  console.error('recorder failed:', e.message);
  // partial file is retained at the path from the start call — attempt to use it
}

Prevention

When it happens

Trigger: Calling recording stop when the recorder process (e.g. screencapture/ffmpeg spawned at start) crashed, was killed, failed due to lost screen-recording permission, or hit a disk/encoder error, so its exit code is nonzero.

Common situations: Screen Recording permission revoked in System Settings mid-recording; recorder binary missing or incompatible after an OS update; encoder failure on very long recordings; machine sleep or user switching displays killing the capture session.

Related errors


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

Appendix: source

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

      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;
    await browser.close().catch(() => {});
    const owned = [...rec.entries()];
    for (const [, recording] of owned) requestRecordingStop(recording);

View on GitHub (pinned to 73e0f67d83)