Skip to content

Fix invalid Windows recording finalization#314

Merged
webadderall merged 1 commit intowebadderallorg:mainfrom
soufian3hm:fix/windows-recording-validation
Apr 22, 2026
Merged

Fix invalid Windows recording finalization#314
webadderall merged 1 commit intowebadderallorg:mainfrom
soufian3hm:fix/windows-recording-validation

Conversation

@soufian3hm
Copy link
Copy Markdown
Contributor

@soufian3hm soufian3hm commented Apr 21, 2026

Summary

  • require strict video validation before a recording is returned as successful
  • route Windows native capture through display coordinates instead of the fragile windowHandle branch
  • validate muxed output before replacing raw captures and stop the renderer from opening an unvalidated fallback path
  • skip unusable tiny/invalid recordings during recovery lookup

Testing

  • npx tsc --noEmit
  • npx vitest --run electron/ipc/recording/diagnostics.test.ts electron/ipc/windowsCaptureSelection.test.ts src/hooks/useScreenRecorder.test.ts
  • npx biome check --formatter-enabled=false --assist-enabled=false electron/ipc/recording/diagnostics.test.ts electron/ipc/recording/diagnostics.ts electron/ipc/recording/mac.ts electron/ipc/recording/windows.ts electron/ipc/register/recording.ts electron/ipc/windowsCaptureSelection.ts src/hooks/useScreenRecorder.ts

Notes

  • npm test currently has one unrelated failure in src/lib/exporter/modernFrameRenderer.test.ts: renderer.capturePixelsForNativeExport is not a function.
  • npm ci without --ignore-scripts fails locally because the bundled Whisper runtime build requires CMake.

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced video validation to reject undersized recordings that cannot contain playable content.
    • Improved error handling and cleanup during video processing and finalization.
    • Strengthened recovery of previous recording sessions by filtering out corrupted or unplayable video files.
    • Added better diagnostics for recording failures on different platforms.
  • Tests

    • Added test coverage for undersized video rejection.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

This PR strengthens video recording validation by introducing a minimum file-size threshold (1024 bytes) and tightening duration parsing logic. It adds validateRecordedVideo calls immediately after FFmpeg mux operations and in recovery paths, with comprehensive error handling and cleanup. Windows capture display resolution selection now uses computed display IDs directly, and finalization flows are updated to reject invalid recordings early rather than logging warnings.

Changes

Cohort / File(s) Summary
Video Validation
electron/ipc/recording/diagnostics.ts, electron/ipc/recording/diagnostics.test.ts
Added MIN_VALID_RECORDED_VIDEO_BYTES constant (1024 bytes) and updated validateRecordedVideo to reject files smaller than threshold. Tightened duration validation to reject null/unparseable duration outputs in addition to invalid durations. New test validates rejection of tiny MP4 container-only outputs.
Mac Native Muxing
electron/ipc/recording/mac.ts
Added post-mux validation via validateRecordedVideo(mixedOutputPath) immediately after FFmpeg succeeds. Enhanced error logging on FFmpeg failure and intermediate file cleanup. Updated finalizeStoredVideo to conditionally record diagnostics for mac-screencapturekit and windows-wgc, then rethrow validation errors instead of logging warnings.
Windows Native Muxing
electron/ipc/recording/windows.ts
Wrapped FFmpeg mux operation in try/catch with validateRecordedVideo(mixedOutputPath) after successful mux. Added best-effort cleanup of intermediate .muxed.mp4 file on any failure before rethrowing error.
Recording Registration & Recovery
electron/ipc/register/recording.ts
Updated Windows native recording to always use resolveWindowsCaptureDisplay() for display bounds. Added post-stop validation via validateRecordedVideo with fallback candidate iteration (newest-first). Strengthened recovered video selection to skip invalid candidates and validate each before returning success.
Display Resolution Logic
electron/ipc/windowsCaptureSelection.ts
Changed resolveWindowsCaptureDisplay to return computed requestedOrPrimaryDisplayId directly instead of casting matched display's id to number.
Recording UI Hook
src/hooks/useScreenRecorder.ts
Strengthened Windows native mux failure handling to require both muxResult?.success and muxResult.path. On failure, logs diagnostics and aborts editor opening. Updated final path assignment to use muxResult.path directly.

Sequence Diagram(s)

sequenceDiagram
    participant FFmpeg
    participant Mux as Mux Handler
    participant Validator as validateRecordedVideo
    participant FileSystem as File System
    
    rect rgba(100, 150, 200, 0.5)
    Note over FFmpeg,FileSystem: Success Path
    FFmpeg->>Mux: FFmpeg mux completes
    Mux->>Validator: validateRecordedVideo(mixedOutput)
    Validator->>FileSystem: Check file size >= 1024 bytes
    Validator->>FFmpeg: Parse duration from output
    alt Valid Video
        Validator->>Mux: Return validation result
        Mux->>FileSystem: Move/finalize recorded video
    end
    end
    
    rect rgba(200, 100, 100, 0.5)
    Note over FFmpeg,FileSystem: Failure Path
    alt FFmpeg mux fails
        FFmpeg->>Mux: Error/stderr
        Mux->>FileSystem: Cleanup intermediate file
        Mux->>Mux: Rethrow error
    else Validation fails
        Validator->>Mux: Reject (too small or invalid duration)
        Mux->>FileSystem: Cleanup intermediate file
        Mux->>Mux: Rethrow error
    end
    end
Loading
sequenceDiagram
    participant App as App/Hook
    participant RegHandler as Register Handler
    participant Validator as validateRecordedVideo
    participant Recovery as Recovery Path
    
    rect rgba(100, 150, 200, 0.5)
    Note over App,Recovery: Normal Stop Path
    App->>RegHandler: Stop recording requested
    RegHandler->>Validator: validateRecordedVideo(finalPath)
    Validator->>RegHandler: Valid recording + metadata
    RegHandler->>App: Success with duration/fileSize
    end
    
    rect rgba(150, 200, 100, 0.5)
    Note over App,Recovery: Recovery Path
    App->>Recovery: Recover recorded video
    Recovery->>Validator: validateRecordedVideo(candidate1)
    alt Invalid
        Validator->>Recovery: Reject candidate
        Recovery->>Validator: validateRecordedVideo(candidate2)
    else Valid
        Validator->>Recovery: Return metadata
        Recovery->>App: Return validated video
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • webadderall/Recordly#312: Implements the same code-level fixes including MIN_VALID_RECORDED_VIDEO_BYTES threshold, post-mux validation before file operations, tightened finalizeStoredVideo error handling, and Windows display coordinate changes.

Possibly related PRs

  • webadderall/Recordly#262: Modifies muxNativeMacRecordingWithAudio in the same file with overlapping post-mux validation and error handling changes.
  • webadderall/Recordly#142: Calls muxNativeMacRecordingWithAudio in recovery path, directly affected by validation flow changes in this PR.
  • webadderall/Recordly#259: Modifies native muxing code paths with FFmpeg filter construction changes that interact with the post-mux validation added here.

Suggested labels

Checked

Poem

🐰 Hop hop, the videos now pass their tests,
Too-tiny outputs fail—no second guests,
We validate before we move and mux,
Clean up on errors—what splendid luck!
Duration checked, file size assured,
Recording quality now procured!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 The title accurately summarizes the main objective of the changeset: fixing invalid Windows recording finalization through strict validation.
Description check ✅ Passed The PR description provides a clear summary of changes, testing commands, and relevant notes, though it deviates from the template structure without covering all required sections.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
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.

Caution

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

⚠️ Outside diff range comments (2)
electron/ipc/windowsCaptureSelection.ts (1)

34-40: ⚠️ Potential issue | 🟠 Major

Keep displayId aligned with the returned bounds.

When a requested display ID is finite but no longer present in allDisplays, matchedDisplay falls back to primaryDisplay while Line 39 still returns the stale requested ID. That can send the Windows helper mismatched displayId and primary-display coordinates.

🐛 Proposed fix
 	return {
-		displayId: requestedOrPrimaryDisplayId,
+		displayId: matchedDisplay.id,
 		bounds: matchedDisplay.bounds,
 	};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/windowsCaptureSelection.ts` around lines 34 - 40, The returned
displayId can be stale when requestedOrPrimaryDisplayId doesn't match any entry
in allDisplays and matchedDisplay falls back to primaryDisplay; update the
return to use the actual matched display's id so bounds and displayId always
correspond (e.g., set displayId to matchedDisplay.id or
String(matchedDisplay.id) to match the type used elsewhere) while keeping
bounds: matchedDisplay.bounds; adjust the conversion to String only if the
surrounding code expects a string.
electron/ipc/register/recording.ts (1)

580-606: ⚠️ Potential issue | 🟠 Major

Preserve Windows audio sidecar paths when returning a validated fallback.

Lines 587-588 clear windowsSystemAudioPath / windowsMicAudioPath before Line 606 returns success. The renderer then calls muxNativeWindowsRecording, but the mux handler sees no audio paths and finalizes video-only.

🐛 Proposed fix
         console.error('Failed to stop native Windows capture:', error)
         const fallbackPath = windowsCaptureTargetPath
+        const fallbackSystemAudioPath = windowsSystemAudioPath
+        const fallbackMicrophonePath = windowsMicAudioPath
         setWindowsNativeCaptureActive(false)
         setNativeScreenRecordingActive(false)
         setWindowsCaptureProcess(null)
         setWindowsCaptureTargetPath(null)
         setWindowsCaptureStopRequested(false)
         setWindowsCapturePaused(false)
-        setWindowsSystemAudioPath(null)
-        setWindowsMicAudioPath(null)
         setWindowsPendingVideoPath(null)

         if (fallbackPath) {
           try {
             await fs.access(fallbackPath)
             const validation = await validateRecordedVideo(fallbackPath)
             setWindowsPendingVideoPath(fallbackPath)
             recordNativeCaptureDiagnostics({
               backend: 'windows-wgc',
               phase: 'stop',
               outputPath: fallbackPath,
-              systemAudioPath: windowsSystemAudioPath,
-              microphonePath: windowsMicAudioPath,
+              systemAudioPath: fallbackSystemAudioPath,
+              microphonePath: fallbackMicrophonePath,
               processOutput: windowsCaptureOutputBuffer.trim() || undefined,
               fileSizeBytes: validation.fileSizeBytes,
               error: String(error),
             })
             return { success: true, path: fallbackPath }
           } catch {
             // File is absent or failed validation.
           }
         }
+
+        setWindowsSystemAudioPath(null)
+        setWindowsMicAudioPath(null)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/register/recording.ts` around lines 580 - 606, The code clears
windowsSystemAudioPath and windowsMicAudioPath before returning a validated
fallback, causing muxNativeWindowsRecording to not see audio sidecars; change
the order so you preserve audio paths when returning the fallback: do not call
setWindowsSystemAudioPath(null) or setWindowsMicAudioPath(null) before the
success return in the block that validates fallbackPath (keep setting
setWindowsPendingVideoPath(fallbackPath) and call recordNativeCaptureDiagnostics
with the preserved windowsSystemAudioPath/windowsMicAudioPath), or move the two
nulling calls to after the return/after muxing; reference the state setters
setWindowsSystemAudioPath, setWindowsMicAudioPath, setWindowsPendingVideoPath
and the validateRecordedVideo/recordNativeCaptureDiagnostics usage to locate the
fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@electron/ipc/register/recording.ts`:
- Around line 580-606: The code clears windowsSystemAudioPath and
windowsMicAudioPath before returning a validated fallback, causing
muxNativeWindowsRecording to not see audio sidecars; change the order so you
preserve audio paths when returning the fallback: do not call
setWindowsSystemAudioPath(null) or setWindowsMicAudioPath(null) before the
success return in the block that validates fallbackPath (keep setting
setWindowsPendingVideoPath(fallbackPath) and call recordNativeCaptureDiagnostics
with the preserved windowsSystemAudioPath/windowsMicAudioPath), or move the two
nulling calls to after the return/after muxing; reference the state setters
setWindowsSystemAudioPath, setWindowsMicAudioPath, setWindowsPendingVideoPath
and the validateRecordedVideo/recordNativeCaptureDiagnostics usage to locate the
fix.

In `@electron/ipc/windowsCaptureSelection.ts`:
- Around line 34-40: The returned displayId can be stale when
requestedOrPrimaryDisplayId doesn't match any entry in allDisplays and
matchedDisplay falls back to primaryDisplay; update the return to use the actual
matched display's id so bounds and displayId always correspond (e.g., set
displayId to matchedDisplay.id or String(matchedDisplay.id) to match the type
used elsewhere) while keeping bounds: matchedDisplay.bounds; adjust the
conversion to String only if the surrounding code expects a string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 619d8972-3973-46b5-b61b-99fdc442ecd1

📥 Commits

Reviewing files that changed from the base of the PR and between 4005687 and 726808a.

📒 Files selected for processing (7)
  • electron/ipc/recording/diagnostics.test.ts
  • electron/ipc/recording/diagnostics.ts
  • electron/ipc/recording/mac.ts
  • electron/ipc/recording/windows.ts
  • electron/ipc/register/recording.ts
  • electron/ipc/windowsCaptureSelection.ts
  • src/hooks/useScreenRecorder.ts

@webadderall webadderall merged commit 235a01c into webadderallorg:main Apr 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants