Skip to content

Fix: stream long recordings to disk and patch WebM duration on save (#616)#658

Merged
siddharthvaddem merged 4 commits into
siddharthvaddem:mainfrom
neurot1cal:fix/streaming-recording-duration
May 27, 2026
Merged

Fix: stream long recordings to disk and patch WebM duration on save (#616)#658
siddharthvaddem merged 4 commits into
siddharthvaddem:mainfrom
neurot1cal:fix/streaming-recording-duration

Conversation

@neurot1cal
Copy link
Copy Markdown
Contributor

@neurot1cal neurot1cal commented May 26, 2026

What this fixes

Recordings longer than ~10 minutes silently fail to save (#616). The renderer buffers the whole WebM as a Blob[], then on stop makes several in-memory copies (fixWebmDurationarrayBufferBuffer.from) before writing. A long 1080p recording duplicates hundreds of MB several times in the renderer, exceeds Electron's renderer memory limit, and the renderer crashes silently — no file saved, no error surfaced.

The fix (two parts)

1. Stream chunks to disk — originally @Amanuel2x's contribution in #617, preserved here with credit. An fs.WriteStream opens in the main process at recording start; each ~1s ondataavailable chunk goes straight to disk over two new IPC calls (open-recording-stream, append-recording-chunk), so the renderer never holds more than a single chunk. A full in-memory fallback is preserved for environments where the IPC stream can't open.

2. Patch the WebM Duration header on disk after the stream closes. Browser MediaRecorder writes WebM with no Duration element, so streamed files would otherwise save with duration=N/A and the editor's seek bar, timeline, and any scrub/trim break. The pre-streaming code patched the in-renderer blob; with streaming the renderer never holds the bytes, so a new electron/recording/webm-duration.ts module rewrites the Duration element on disk. It writes to a temp file and renames in place, so a crash mid-write can never corrupt the recording — the original survives intact. Any read/parse/write failure logs and returns a non-fatal result rather than throwing; the file is still playable, the patch is best-effort.

Streaming is opt-in. The screen recorder and the browser-only webcam recorder stream to disk. Native-capture webcam sidecars (Windows, macOS) keep buffering in memory, because their finalize path reads the recorder blob directly to attach the webcam track — streaming those would silently drop the webcam. Making streaming opt-in (a recorder only streams when given a recordingId + fileName) closes that gap.

Verified

  • tsc --noEmit clean
  • biome check clean
  • vitest 166/166 passing
  • Rebased clean onto current main (5467796); single squashed commit.

Not verified remotely (worth a smoke-test before/after merge)

I built and verified this from a remote session, so I could not run a real long recording end-to-end on a desktop. No unit test exercises the Electron fs.WriteStream IPC path or the native-webcam finalize path. Before relying on it, worth confirming on a desktop:

  • A >10 min screen recording saves and the editor seek bar / timeline read a real duration.
  • A native-webcam recording (Win/macOS) still attaches the webcam track correctly (should be unchanged — those sidecars don't stream).

Notes

  • Closes #616, Supersedes #617.
  • @Amanuel2x credited via Co-Authored-By on the commit.
  • I verified the EBML section IDs used by the on-disk patcher (0x8538067 / 0x549a966) against @fix-webm-duration/parser's src/lib/sections.js — they're the length-descriptor-stripped form the parser uses as lookup keys, not the canonical 4-byte IDs.

@siddharthvaddem — fresh single-commit PR off current main, per your ask. Happy to adjust anything.

Summary by CodeRabbit

  • New Features
    • Stream recordings directly to disk per output (screen and optional webcam) with automatic in-memory fallback and renderer APIs to control streams.
  • Bug Fixes
    • Patch WebM duration metadata for streamed recordings to improve playback/seek.
    • Better handling/cleanup of partial or failed streams and tolerant inclusion of webcam data.
  • Tests
    • Expanded tests for streaming lifecycle, ordering, error handling, fallback, discard/finalize, and concurrency.

Review Change Stack

Recordings longer than ~10 minutes silently fail to save (siddharthvaddem#616). The
renderer buffers the whole WebM as a Blob[], then on stop makes several
in-memory copies (fixWebmDuration -> arrayBuffer -> Buffer.from) before
writing. A long 1080p recording duplicates hundreds of MB several times
in the renderer, exceeds Electron's memory limit, and the renderer
crashes silently with no file saved.

Two changes:

1. Stream chunks to disk (originally @Amanuel2x's contribution in siddharthvaddem#617).
   Open an fs.WriteStream in the main process at recording start and send
   each ~1s ondataavailable chunk straight to disk over two new IPC calls
   (open-recording-stream, append-recording-chunk), so the renderer never
   holds more than a single chunk. A full in-memory fallback is preserved
   for environments where the IPC stream cannot open.

2. Patch the WebM Duration header on disk after the stream closes. Browser
   MediaRecorder writes WebM with no Duration element, so streamed files
   save with duration=N/A and the editor's seek bar, timeline, and any
   scrub/trim break. A new electron/recording/webm-duration.ts module
   rewrites the Duration element, writing to a temp file and renaming in
   place so a crash mid-write cannot corrupt the recording.

Streaming is opt-in: the screen recorder and the browser-only webcam
recorder stream to disk; native-capture webcam sidecars (Windows, macOS)
keep buffering in-memory, since their finalize path reads the recorder
blob directly to attach the webcam track.

Verified: tsc --noEmit clean; biome clean; vitest 166/166.

Closes siddharthvaddem#616
Supersedes siddharthvaddem#617

Co-Authored-By: Amanuel <amanuel@localboostnetworking.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

Adds renderer-to-main chunked MediaRecorder streaming via preload IPC and a main-process RecordingStreamRegistry; session finalization skips re-writing streamed files and best-effort patches WebM duration headers on disk when durationMs is provided.

Changes

Recording Chunk Streaming

Layer / File(s) Summary
WebM duration patcher
electron/recording/webm-duration.ts
patchWebmDurationOnDisk and DurationPatchResult parse and atomically patch WebM duration headers on disk.
RecordingStreamRegistry & tests
electron/ipc/recordingStream.ts, electron/ipc/recordingStream.test.ts
New RecordingStreamRegistry manages per-file WriteStreams (open/append/finalize/discard) and registerRecordingStreamHandlers registers IPC handlers; tests validate lifecycle, reopen, error, and discard behaviors.
Preload bridge & types
electron/preload.ts, electron/electron-env.d.ts
Preload exposes openRecordingStream(fileName), appendRecordingChunk(fileName, chunk), and closeRecordingStream(fileName); Window.electronAPI declarations updated to match.
Handlers integration into session persistence
electron/ipc/handlers.ts
Instantiates registry and handler wiring; storeRecordedSessionFiles finalizes stream outputs via registry.finalize and writes buffered bytes only when no stream existed; invokes patchWebmDurationOnDisk for streamed files when payload.durationMs is valid.
RecorderHandle and integration
src/hooks/recorderHandle.ts, src/hooks/useScreenRecorder.ts, src/lib/recordingSession.ts, vitest.config.ts
Adds createRecorderHandle that optionally streams chunks to disk via electronAPI, serializes writes, resolves an empty Blob when streaming succeeded; useScreenRecorder uses this handle, finalization detects streamed outputs and avoids redundant in-memory writes; StoreRecordedSessionInput.durationMs added; test glob updated to include electron/.
Recorder & renderer tests
src/hooks/recorderHandle.test.ts, electron/ipc/recordingStream.test.ts
Vitest suites validate buffering, fallback, write-order, finalize/discard semantics, reopen semantics, and error propagation for both renderer handle and main registry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • siddharthvaddem

🎬 tiny chunks to disk at night,
the recorder buffers, the main writes right.
patch the webm, rename with care,
kinda cursed but tidy — lowkey rare. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the two main changes: streaming long recordings to disk and patching WebM duration on save, directly addressing issue #616.
Description check ✅ Passed Description covers all critical sections: problem statement, two-part fix with implementation details, verification steps, testing notes, and related issue closure. Well-structured and comprehensive despite deviating from template format.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 727e395fcf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/hooks/useScreenRecorder.ts Outdated
Comment on lines +164 to +168
recorder.onstop = () => {
resolve(new Blob(chunks, { type: mimeType }));
if (streamFailed) {
// Not streaming, or the stream failed to open — return the full
// in-memory blob (the buffered fallback).
resolve(new Blob(fallbackChunks, { type: mimeType }));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wait for chunk flush before resolving stop

ondataavailable offloads each chunk to event.data.arrayBuffer().then(...), but onstop resolves immediately without waiting for those async conversions/appends to finish. Finalization then calls storeRecordedSession, which closes and removes the write stream (screenWs.end() in electron/ipc/handlers.ts), so late appendRecordingChunk calls can arrive after close and be dropped, truncating the saved recording under load or with large final chunks.

Useful? React with 👍 / 👎.

Comment thread src/hooks/useScreenRecorder.ts Outdated

recorder.start(RECORDER_TIMESLICE_MS);
return { recorder, recordedBlobPromise };
return { recorder, recordedBlobPromise, streaming: !streamFailed };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recompute streaming flag after open-stream failure

The returned streaming flag is computed once at construction time (!streamFailed) before openRecordingStream resolves. If stream opening fails later, streamFailed flips and chunks are buffered in memory fallback, but finalize still treats this recorder as streamed and sends empty videoData; since no active stream exists in main, storeRecordedSessionFiles skips both stream-close and buffer-write paths, so the recording can be lost in exactly the failure case the fallback is meant to cover.

Useful? React with 👍 / 👎.

Comment on lines 424 to 425
window.electronAPI?.discardCursorTelemetry(activeRecordingId);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Close streamed writers when recording is discarded

When a recording is canceled/restarted, this early return bypasses storeRecordedSession, but write streams are only ended/deleted inside storeRecordedSessionFiles in electron/ipc/handlers.ts. For streamed recordings, that leaves entries in activeWriteStreams and open/partial files on disk for each discarded run, causing descriptor/resource leaks and stale artifacts over repeated cancels.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useScreenRecorder.ts (1)

423-426: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Discard path doesn’t close active disk stream in main process.

At Line 423–426, discard returns before storeRecordedSession, but main-side stream cleanup currently happens inside storeRecordedSessionFiles. This can leak activeWriteStreams entries and leave partial files around.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useScreenRecorder.ts` around lines 423 - 426, The discard branch
returns early (when discardRecordingId.current === activeRecordingId) and calls
window.electronAPI?.discardCursorTelemetry(activeRecordingId) but never triggers
the main-process stream cleanup done in storeRecordedSessionFiles, which leaks
entries in activeWriteStreams and leaves partial files; modify the discard path
in useScreenRecorder (the block referencing discardRecordingId,
activeRecordingId and window.electronAPI?.discardCursorTelemetry) to also invoke
the main-process cleanup — either call the existing storeRecordedSessionFiles
cleanup routine (or expose and call a new close/cleanup IPC like
closeActiveWriteStream/cleanupRecording with the activeRecordingId) before
returning so activeWriteStreams is closed and partial files are removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/ipc/handlers.ts`:
- Around line 2157-2161: The IPC handler that opens a recording stream returns
success immediately after calling createWriteStream (using
resolveRecordingOutputPath, createWriteStream, activeWriteStreams, recordingId,
ws), but the stream can emit an asynchronous 'error' before it's actually
writable; change the handler to wait for the stream to emit 'open' (or an
'error') before returning success: create the write stream, attach one-time
listeners for 'open' to set activeWriteStreams and return { success: true } and
for 'error' to clean up and return { success: false, error: <message> } (do not
set activeWriteStreams until 'open' fires), ensuring the renderer only sees
success after the stream is confirmed writable.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 114-117: The code computes a one-time "streaming" snapshot which
can go stale and cause data loss; change all uses of the snapshot to compute
streaming at runtime from the current state (e.g., derive streaming as
Boolean(streamTarget) && !streamFailed && streamReady) instead of using the
precomputed "streaming" variable; update places that decide whether to
finalize/serialize fallback (including the finalize routine and any logic around
pendingChunks, streamReady, streamFailed, and streamTarget) to consult this
runtime-derived value so a late stream-open failure will fall back to
serializing the buffered blob.
- Around line 126-128: The appendRecordingChunk calls are fire-and-forget so
onstop can resolve before final writes complete, leading to truncated files;
update the code paths that push chunks (the loop over pendingChunks and the
other occurrences around lines 147-157 and 164-172) to collect the Promises
returned by window.electronAPI.appendRecordingChunk and await them (e.g., await
Promise.all(pendingChunks.map(chunk =>
window.electronAPI.appendRecordingChunk(streamTarget.recordingId, chunk))))
before allowing onstop to finish or before calling storeRecordedSession; ensure
appendRecordingChunk invocations are awaited and propagate/reject errors so
storeRecordedSession only runs after all append promises settle.

---

Outside diff comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 423-426: The discard branch returns early (when
discardRecordingId.current === activeRecordingId) and calls
window.electronAPI?.discardCursorTelemetry(activeRecordingId) but never triggers
the main-process stream cleanup done in storeRecordedSessionFiles, which leaks
entries in activeWriteStreams and leaves partial files; modify the discard path
in useScreenRecorder (the block referencing discardRecordingId,
activeRecordingId and window.electronAPI?.discardCursorTelemetry) to also invoke
the main-process cleanup — either call the existing storeRecordedSessionFiles
cleanup routine (or expose and call a new close/cleanup IPC like
closeActiveWriteStream/cleanupRecording with the activeRecordingId) before
returning so activeWriteStreams is closed and partial files are removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c762bce-992a-471d-836a-45251566cf2b

📥 Commits

Reviewing files that changed from the base of the PR and between 5467796 and 727e395.

📒 Files selected for processing (6)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/preload.ts
  • electron/recording/webm-duration.ts
  • src/hooks/useScreenRecorder.ts
  • src/lib/recordingSession.ts

Comment thread electron/ipc/handlers.ts Outdated
Comment thread src/hooks/useScreenRecorder.ts Outdated
Comment thread src/hooks/useScreenRecorder.ts Outdated
Addresses the review feedback on siddharthvaddem#658 (CodeRabbit + Codex) and the
structural notes from the quality pass.

Correctness:
- Compute the recorder's streaming state at finalize time, not at
  construction. A stream that fails to open is now reported as
  not-streamed, so its buffered chunks are saved as a complete in-memory
  fallback instead of being dropped (was total data loss on open failure).
- Await every in-flight chunk write before onstop resolves, so the main
  process never closes the write stream while a final chunk is still in
  flight (was truncating the tail of a recording under load).
- Open the disk write stream by awaiting its 'open' event, so a bad path
  or permission error rejects up front instead of being acknowledged as
  success and then silently dropping bytes.
- Close the stream and remove the partial file when a streamed recording
  is discarded or fails, so cancelled/failed runs don't leak descriptors
  or orphan partial recordings.
- Surface a mid-stream write failure as a rejected recording rather than
  saving a silently truncated file.

Structure:
- Extract the streaming concern into electron/ipc/recordingStream.ts
  (RecordingStreamRegistry) and src/hooks/recorderHandle.ts, out of the
  2.8k-line handlers.ts and the screen-recorder hook.
- Key write streams by output file name, removing the implicit
  recordingId/+1 contract that spanned the IPC boundary.
- Collapse the duplicated screen/webcam finalize blocks into one helper
  and the repeated duration-validity guard into one check; patch the
  screen and webcam durations in parallel.

Adds unit tests for the registry (real temp-dir fs) and the recorder
handle state machine (open-failure fallback, in-order writes awaited
before stop, mid-stream failure). Extends the vitest include glob to
collect electron-side tests.

Verified: tsc --noEmit clean; biome clean; vitest 180/180.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@neurot1cal
Copy link
Copy Markdown
Contributor Author

Pushed f3c5b8a addressing the review. What changed and where:

CodeRabbit / Codex findings

  • streaming flag computed too early (data loss on open failure). The handle now exposes isStreaming() evaluated at finalize time. A stream that fails to open is reported as not-streamed, and its buffered chunks are saved as a complete in-memory fallback rather than dropped. Test: "falls back to a complete in-memory blob when the stream fails to open".
  • Fire-and-forget appends / onstop resolving before writes land (truncation). Chunk writes are serialized on a queue and onstop awaits every in-flight write before resolving, so the main process never closes the stream mid-write. Test: "waits for in-flight chunk writes before stop resolves".
  • open-recording-stream acks success before the stream is writable. RecordingStreamRegistry.open() awaits the stream's open/error event; a bad path/permission error rejects up front. Test: "rejects open when the target path is not writable".
  • Discard path leaks streams + partial files. Added a close-recording-stream IPC; the renderer calls it on discard/error to close the stream and unlink the partial. Test: "discard closes the stream and removes the partial file".

Structural

  • Streaming lifecycle lifted out of the ~2.8k-line handlers.ts into electron/ipc/recordingStream.ts, and out of the hook into src/hooks/recorderHandle.ts.
  • Write streams are keyed by output file name, removing the recordingId/+1 contract that spanned the IPC boundary.
  • Duplicated screen/webcam finalize blocks collapsed into one helper; the two duration patches run in parallel.
  • A mid-stream write failure now rejects the recording instead of silently saving a truncated file.

Verified locally: tsc --noEmit clean, biome clean, vitest 180/180 (14 new tests).

One caveat unchanged from the PR description: still worth a desktop smoke-test of a real >10-min recording plus a native webcam before relying on it, since no unit test exercises the live Electron + MediaRecorder path end to end.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/recorderHandle.ts`:
- Around line 77-88: openPromise.then currently has no rejection handler which
can leave mode as "pending" on unexpected IPC failures; add a .catch on
openPromise that sets mode = "buffering", ensures streamOpened stays false,
preserves memoryChunks (or enqueues them as currently done when buffering), and
optionally logs the error so the code can proceed to finalizeBlob() safely;
update the promise chain around openPromise (the block referencing openPromise,
mode, streamOpened, memoryChunks, and enqueueWrite) to include this .catch and
make sure the error branch mirrors the successful-path transition to buffering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa0974de-e221-4710-b2f7-56db5c8ac597

📥 Commits

Reviewing files that changed from the base of the PR and between 727e395 and f3c5b8a.

📒 Files selected for processing (9)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/ipc/recordingStream.test.ts
  • electron/ipc/recordingStream.ts
  • electron/preload.ts
  • src/hooks/recorderHandle.test.ts
  • src/hooks/recorderHandle.ts
  • src/hooks/useScreenRecorder.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (1)
  • vitest.config.ts

Comment thread src/hooks/recorderHandle.ts Outdated
@neurot1cal
Copy link
Copy Markdown
Contributor Author

Re-review requested after f3c5b8a, which addresses all prior findings (the four correctness issues plus the structural extraction). Verified locally: tsc clean, biome clean, vitest 180/180.

@coderabbitai full review
@codex review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

@neurot1cal on it — kicking off a full re-review of f3c5b8a now. the streaming lifecycle refactor + all four correctness fixes look well-scoped from the summaries; let's see how the full pass lands.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3c5b8a65d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 359 to +363
const webcamBlob = await activeWebcamRecorder.recordedBlobPromise.catch(() => null);
if (webcamBlob && webcamBlob.size > 0) {
fixedWebcamBlob = await fixWebmDuration(webcamBlob, duration);
if (!activeWebcamRecorder.isStreaming() && webcamBlob && webcamBlob.size > 0) {
const fixedWebcamBlob = await fixWebmDuration(webcamBlob, duration);
webcamVideoData = await fixedWebcamBlob.arrayBuffer();
} else if (activeWebcamRecorder.isStreaming()) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Discard failed webcam stream before saving screen-only session

When the webcam recorder is using disk streaming and a chunk write fails, recordedBlobPromise is swallowed to null here, so webcam is omitted from storeRecordedSession while the screen path can still succeed. In that path savedToDisk becomes true later, so the finally block skips activeWebcamRecorder.discard(), leaving the webcam stream/file unclosed and orphaned after a partial write failure. Please either fail the whole save or explicitly discard the webcam stream when this branch is hit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/hooks/recorderHandle.ts (1)

77-88: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle openPromise rejection explicitly.

Line 77 currently chains void openPromise.then(...) without a rejection path; unexpected IPC-level rejection can leave this in a kinda cursed "pending" state and trigger unhandled rejection noise. Add a .catch that flips to buffering fallback.

nit: cleaner/fail-safe promise chain
-	void openPromise.then((result) => {
-		if (result.success) {
-			streamOpened = true;
-			mode = "streaming";
-			for (const chunk of memoryChunks) {
-				enqueueWrite(chunk);
-			}
-			memoryChunks.length = 0;
-		} else {
-			mode = "buffering";
-		}
-	});
+	void openPromise
+		.then((result) => {
+			if (result.success) {
+				streamOpened = true;
+				mode = "streaming";
+				for (const chunk of memoryChunks) {
+					enqueueWrite(chunk);
+				}
+				memoryChunks.length = 0;
+			} else {
+				mode = "buffering";
+			}
+		})
+		.catch(() => {
+			mode = "buffering";
+		});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/recorderHandle.ts` around lines 77 - 88, The promise openPromise is
only handled with then(...) which leaves rejections unhandled; update the chain
on the openPromise used around streamOpened/mode/memoryChunks to add a
.catch(...) that sets mode = "buffering", ensures streamOpened is false, drains
or preserves memoryChunks as before (do not enqueueWrite), and optionally logs
the error; specifically modify the openPromise.then(...) callback for the open
path (where enqueueWrite is called) to append a .catch that flips to the
buffering fallback and prevents unhandled rejection noise.
🧹 Nitpick comments (1)
vitest.config.ts (1)

7-9: 💤 Low value

heads up: electron/ tests running in jsdom might get weird

most electron main-process code uses node APIs (fs, path, etc.) which jsdom doesn't provide natively. if these tests are passing (sounds like they are per the 180/180 report), you're probably mocking things properly — just noting that if future electron tests start acting cursed, you might want per-directory environment overrides:

// alternative: use vitest workspace or inline comments like
// `@vitest-environment` node

lowkey fine to leave as-is if it works, just a heads up for the future.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vitest.config.ts` around lines 7 - 9, The current Vitest config sets
environment: "jsdom" for all tests (see environment in vitest.config.ts) which
can break Electron main-process tests that rely on Node APIs; to fix, either add
per-directory/targeted overrides in the Vitest configuration to run tests under
node for the electron folder (override environment for files matching
"{electron}/**/*.{test,spec}..." or similar) or add inline file-level directives
in failing electron tests (e.g., "`@vitest-environment` node") so main-process
code uses Node APIs; update the include/exclude patterns if needed to ensure
electron tests match the node-environment override.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/recorderHandle.ts`:
- Around line 60-69: The writeChain.then block can reject if
api.appendRecordingChunk throws, leaving appendError null and causing
isStreaming() to report true; wrap the await api.appendRecordingChunk(fileName,
buffer) call in a try/catch inside the writeChain.then handler (the function
that currently sets appendError on result.success) so that any thrown error is
caught and assigned to appendError (e.g., appendError = caughtError or a new
Error with message), and do not rethrow so the promise chain stays
settled—update the handler around writeChain, appendError, and
api.appendRecordingChunk to normalize both rejection and { success:false } into
appendError.

---

Duplicate comments:
In `@src/hooks/recorderHandle.ts`:
- Around line 77-88: The promise openPromise is only handled with then(...)
which leaves rejections unhandled; update the chain on the openPromise used
around streamOpened/mode/memoryChunks to add a .catch(...) that sets mode =
"buffering", ensures streamOpened is false, drains or preserves memoryChunks as
before (do not enqueueWrite), and optionally logs the error; specifically modify
the openPromise.then(...) callback for the open path (where enqueueWrite is
called) to append a .catch that flips to the buffering fallback and prevents
unhandled rejection noise.

---

Nitpick comments:
In `@vitest.config.ts`:
- Around line 7-9: The current Vitest config sets environment: "jsdom" for all
tests (see environment in vitest.config.ts) which can break Electron
main-process tests that rely on Node APIs; to fix, either add
per-directory/targeted overrides in the Vitest configuration to run tests under
node for the electron folder (override environment for files matching
"{electron}/**/*.{test,spec}..." or similar) or add inline file-level directives
in failing electron tests (e.g., "`@vitest-environment` node") so main-process
code uses Node APIs; update the include/exclude patterns if needed to ensure
electron tests match the node-environment override.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c131ee22-9b2c-47a0-a447-6c7543da557d

📥 Commits

Reviewing files that changed from the base of the PR and between 5467796 and f3c5b8a.

📒 Files selected for processing (11)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/ipc/recordingStream.test.ts
  • electron/ipc/recordingStream.ts
  • electron/preload.ts
  • electron/recording/webm-duration.ts
  • src/hooks/recorderHandle.test.ts
  • src/hooks/recorderHandle.ts
  • src/hooks/useScreenRecorder.ts
  • src/lib/recordingSession.ts
  • vitest.config.ts

Comment thread src/hooks/recorderHandle.ts
Addresses the CodeRabbit + Codex re-review of the prior commit.

- Normalize a rejected append (channel/handler error, not just a
  { success: false } result) into appendError, so the write queue never
  rejects and isStreaming() stays consistent after a failure (CodeRabbit).
- Handle a rejected open-stream IPC the same as a failed open: fall back
  to in-memory buffering instead of leaving the recorder stuck "pending"
  with an unhandled rejection (CodeRabbit).
- Discard a streamed webcam whose write failed even when the screen save
  succeeds. The cleanup gate is now per-recorder, so a webcam omitted from
  a successful screen-only save no longer leaks its stream and partial
  file (Codex).

Adds tests for the rejected-append and rejected-open paths.

Verified: tsc --noEmit clean; biome clean; vitest 182/182.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@neurot1cal
Copy link
Copy Markdown
Contributor Author

Pushed 36d7d2b addressing the re-review:

  • Rejected append normalized into appendError (CodeRabbit, recorderHandle.ts) — a thrown/rejected appendRecordingChunk, not just a { success: false } result, is now caught into appendError, so the write queue never rejects and isStreaming() stays consistent after a failure. Test: "treats a rejected append the same as a failed write".
  • Rejected open-stream IPC handled as a failed open (CodeRabbit, recorderHandle.ts) — falls back to in-memory buffering instead of leaving the recorder stuck "pending" with an unhandled rejection. Test: "falls back to in-memory buffering when the open IPC call rejects".
  • Webcam stream no longer orphaned on a screen-only save (Codex, useScreenRecorder.ts) — the cleanup gate is now per-recorder, so a webcam whose disk write failed (and was therefore omitted from an otherwise-successful save) is discarded rather than leaking its stream and partial file.

Verified locally: tsc clean, biome clean, vitest 182/182 (2 new tests).

@coderabbitai full review
@codex review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

@neurot1cal nice — those three are exactly the right fixes. the appendError normalization is clean (thrown rejections and { success: false } both funnel into the same path, no weird mid-queue state), the rejected-open fallback closes the last "recorder stuck pending" hole, and the per-recorder discard gate in useScreenRecorder was lowkey the sneakiest leak of the bunch. 182/182 with 2 new targeted tests is a good signal. kicking off the full pass now.

🐇 ✨

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36d7d2bdd0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/hooks/recorderHandle.ts Outdated
Comment on lines +80 to +82
fileName && api?.openRecordingStream
? api.openRecordingStream(fileName)
: Promise.resolve({ success: false });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard streaming mode on append IPC availability

If openRecordingStream exists but appendRecordingChunk is unavailable (for example during renderer/main version skew), this path still opens the stream and switches mode to "streaming". In that state enqueueWrite immediately returns on every chunk because appendRecordingChunk is missing, buffered chunks are cleared, and recordedBlobPromise resolves as if streaming succeeded, producing an empty/truncated saved recording instead of falling back to in-memory buffering.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/hooks/recorderHandle.ts (1)

141-145: 💤 Low value

lowkey risky if called before recordedBlobPromise settles

if someone calls discard() while openPromise is still pending, streamOpened is false and we return early — then the stream opens successfully afterward and gets orphaned. yes the docstring implies calling after the promise settles, but adding a defensive await makes this bulletproof.

🛡️ defensive fix
 async function discard(): Promise<void> {
+    await openPromise.catch(() => undefined);
     if (streamOpened && fileName && api?.closeRecordingStream) {
         await api.closeRecordingStream(fileName);
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/recorderHandle.ts` around lines 141 - 145, discard() can return
early if called before the recording/open promise settles, leaving a
later-opened stream orphaned; to fix, have discard await the promise that
signals the stream open (e.g., await openPromise or recordedBlobPromise) before
checking streamOpened and fileName, then proceed to call
api.closeRecordingStream(fileName) if streamOpened and fileName are set; update
the discard function to await the appropriate Promise
(openPromise/recordedBlobPromise) and keep the existing conditional and await of
api.closeRecordingStream(fileName) to ensure no orphaned streams.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hooks/recorderHandle.ts`:
- Around line 141-145: discard() can return early if called before the
recording/open promise settles, leaving a later-opened stream orphaned; to fix,
have discard await the promise that signals the stream open (e.g., await
openPromise or recordedBlobPromise) before checking streamOpened and fileName,
then proceed to call api.closeRecordingStream(fileName) if streamOpened and
fileName are set; update the discard function to await the appropriate Promise
(openPromise/recordedBlobPromise) and keep the existing conditional and await of
api.closeRecordingStream(fileName) to ensure no orphaned streams.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04073d80-652d-47ae-8f96-672cab1bb530

📥 Commits

Reviewing files that changed from the base of the PR and between f3c5b8a and 36d7d2b.

📒 Files selected for processing (3)
  • src/hooks/recorderHandle.test.ts
  • src/hooks/recorderHandle.ts
  • src/hooks/useScreenRecorder.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/recorderHandle.test.ts
  • src/hooks/useScreenRecorder.ts

Codex re-review: if openRecordingStream exists but appendRecordingChunk
does not (renderer/main version skew), the recorder would open the stream
and switch to streaming mode, but every append silently no-ops and the
save ends up empty. Require both IPC methods before streaming; otherwise
fall back to in-memory buffering. Adds a regression test.

Verified: tsc --noEmit clean; biome clean; vitest 183/183.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@neurot1cal
Copy link
Copy Markdown
Contributor Author

Pushed 5c5cab6 for the version-skew finding (Codex): if openRecordingStream exists but appendRecordingChunk doesn't, the recorder now falls back to in-memory buffering instead of entering streaming mode and saving an empty file. Both stream IPC methods are required before streaming. Regression test added.

tsc clean, biome clean, vitest 183/183.

@coderabbitai full review
@codex review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

@neurot1cal good catch on the version-skew case — that's lowkey one of the more cursed Electron failure modes (preload and main out of sync after an update, silently saving an empty file). requiring both methods before enabling streaming is the right call; partial API presence is worse than no API presence. 183/183 green + regression test is a clean close on that one. kicking off the full pass now.

🐇 ✨

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/hooks/recorderHandle.ts (1)

141-145: 💤 Low value

small edge case around early cancellation

discard() checks streamOpened before calling closeRecordingStream, which means if someone calls discard() while openPromise is still pending and it later succeeds, the stream gets orphaned.

in normal usage (discard after stop + await recordedBlobPromise), this is fine because finalizeBlob awaits openPromise first. but if you ever need early cancellation (user hits cancel mid-recording before any chunks flush), you might want to await openPromise.catch(() => undefined) at the top of discard() too.

probably not urgent since the main process will eventually GC the WriteStream, but worth noting if early cancellation becomes a feature.

safer discard for early cancellation (if needed)
 async function discard(): Promise<void> {
+	await openPromise.catch(() => undefined);
 	if (streamOpened && fileName && api?.closeRecordingStream) {
 		await api.closeRecordingStream(fileName);
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/recorderHandle.ts` around lines 141 - 145, The discard() function
can orphan the recording stream if called before openPromise resolves; update
discard() to first await openPromise.catch(() => undefined) (or otherwise await
it safely) before checking streamOpened/fileName and calling
api.closeRecordingStream, so that if openPromise later sets streamOpened and
fileName the cleanup still runs; reference the discard(), openPromise,
streamOpened, fileName and api.closeRecordingStream symbols when making this
change.
vitest.config.ts (1)

8-8: 💤 Low value

jsdom environment for electron/ tests — should be fine but worth noting

the electron/ tests now run under jsdom which works for the registry/IPC handler tests since they mock fs and don't need actual Electron runtime. if future tests need real Electron APIs, consider a separate config or environment override. lowkey fine for now though.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vitest.config.ts` at line 8, The current vitest include pattern
"{src,electron}/**/*.{test,spec}..." causes electron/* tests to run under the
default jsdom environment which is fine now but may break tests needing real
Electron APIs; update vitest.config.ts to explicitly map the electron test files
to an appropriate environment (e.g., use environmentMatch or a separate test
config so electron/** uses "node" or an Electron-specific env) or split electron
tests into a separate Vitest config so tests that require real Electron APIs
don't run under jsdom; refer to the include pattern and the vitest config export
to locate where to add the environmentMatch or alternate config.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hooks/recorderHandle.ts`:
- Around line 141-145: The discard() function can orphan the recording stream if
called before openPromise resolves; update discard() to first await
openPromise.catch(() => undefined) (or otherwise await it safely) before
checking streamOpened/fileName and calling api.closeRecordingStream, so that if
openPromise later sets streamOpened and fileName the cleanup still runs;
reference the discard(), openPromise, streamOpened, fileName and
api.closeRecordingStream symbols when making this change.

In `@vitest.config.ts`:
- Line 8: The current vitest include pattern
"{src,electron}/**/*.{test,spec}..." causes electron/* tests to run under the
default jsdom environment which is fine now but may break tests needing real
Electron APIs; update vitest.config.ts to explicitly map the electron test files
to an appropriate environment (e.g., use environmentMatch or a separate test
config so electron/** uses "node" or an Electron-specific env) or split electron
tests into a separate Vitest config so tests that require real Electron APIs
don't run under jsdom; refer to the include pattern and the vitest config export
to locate where to add the environmentMatch or alternate config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7514b753-618e-4ff2-9bf0-14caaff81a5a

📥 Commits

Reviewing files that changed from the base of the PR and between 5467796 and 36d7d2b.

📒 Files selected for processing (11)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/ipc/recordingStream.test.ts
  • electron/ipc/recordingStream.ts
  • electron/preload.ts
  • electron/recording/webm-duration.ts
  • src/hooks/recorderHandle.test.ts
  • src/hooks/recorderHandle.ts
  • src/hooks/useScreenRecorder.ts
  • src/lib/recordingSession.ts
  • vitest.config.ts

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
vitest.config.ts (1)

8-8: 💤 Low value

nit: electron tests probably don't need jsdom

the electron/ tests are likely pure node (no DOM), so running them under jsdom adds overhead. you could split environments by path pattern if perf becomes noticeable, but it's not blocking since tests pass.

// example if you ever care:
test: {
  environmentMatchGlobs: [
    ['electron/**', 'node'],
    ['src/**', 'jsdom'],
  ],
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vitest.config.ts` at line 8, The electron tests under the include pattern
"{src,electron}/**/*.{test,spec}..." are running in the default jsdom
environment; update the vitest config to assign a Node environment for electron
tests by adding a test.environmentMatchGlobs entry (e.g., map 'electron/**' ->
'node' and keep 'src/**' -> 'jsdom') so electron/* files run under Node and
avoid jsdom overhead; modify the test config block to include
environmentMatchGlobs accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/ipc/recordingStream.ts`:
- Around line 83-86: In discard, don't swallow all unlink errors; after awaiting
this.endStream(fileName) catch unlink errors and only ignore those with err.code
=== 'ENOENT', rethrow any other error so delete failures surface to the caller
(update the error handler for unlink in the discard method to check err.code and
rethrow non-ENOENT errors) — this keeps the close-recording-stream flow from
reporting success when a real delete failed.

---

Nitpick comments:
In `@vitest.config.ts`:
- Line 8: The electron tests under the include pattern
"{src,electron}/**/*.{test,spec}..." are running in the default jsdom
environment; update the vitest config to assign a Node environment for electron
tests by adding a test.environmentMatchGlobs entry (e.g., map 'electron/**' ->
'node' and keep 'src/**' -> 'jsdom') so electron/* files run under Node and
avoid jsdom overhead; modify the test config block to include
environmentMatchGlobs accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88726d70-fe66-49a9-8763-9c77cd13f11f

📥 Commits

Reviewing files that changed from the base of the PR and between 5467796 and 5c5cab6.

📒 Files selected for processing (11)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/ipc/recordingStream.test.ts
  • electron/ipc/recordingStream.ts
  • electron/preload.ts
  • electron/recording/webm-duration.ts
  • src/hooks/recorderHandle.test.ts
  • src/hooks/recorderHandle.ts
  • src/hooks/useScreenRecorder.ts
  • src/lib/recordingSession.ts
  • vitest.config.ts

Comment on lines +83 to +86
async discard(fileName: string, filePath: string): Promise<void> {
await this.endStream(fileName);
await unlink(filePath).catch(() => undefined);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Discard path hides real delete failures

lowkey risky: Line 85 swallows all unlink errors, so close-recording-stream can report success while a partial recording still exists on disk. Ignore only ENOENT; rethrow the rest.

Suggested fix
 async discard(fileName: string, filePath: string): Promise<void> {
 	await this.endStream(fileName);
-	await unlink(filePath).catch(() => undefined);
+	try {
+		await unlink(filePath);
+	} catch (error) {
+		const err = error as NodeJS.ErrnoException;
+		if (err.code !== "ENOENT") {
+			throw error;
+		}
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/recordingStream.ts` around lines 83 - 86, In discard, don't
swallow all unlink errors; after awaiting this.endStream(fileName) catch unlink
errors and only ignore those with err.code === 'ENOENT', rethrow any other error
so delete failures surface to the caller (update the error handler for unlink in
the discard method to check err.code and rethrow non-ENOENT errors) — this keeps
the close-recording-stream flow from reporting success when a real delete
failed.

@neurot1cal
Copy link
Copy Markdown
Contributor Author

@codex review

(Final commit is 5c5cab6, which fixes the version-skew finding — the prior Codex pass referenced 36d7d2b.)

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@siddharthvaddem siddharthvaddem merged commit b70994b into siddharthvaddem:main May 27, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants