You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On macOS, when the SCStream stops mid-capture, the recording silently truncates. Nothing in the UI changes: the recording indicator stays on, the webcam keeps recording, the cursor track keeps logging, and the timer keeps counting. You discover minutes later, in the editor, that the screen video ends at a fraction of the take.
I lost two takes to this in one session (6:46 recorded → 55 s of video; 8:23 recorded → 115 s of video) and only found the cause by probing the files.
Type
bug
Evidence
Two sessions, full-display capture of a 5K display, mic on, system audio off. Each recording produces four artefacts; here is what each contains:
Session
Screen video
Its inline audio
Webcam
Cursor track
Actually recorded
A
114.89 s
114.68 s
501.84 s
503.4 s
~8:23
B
55.02 s
56.76 s
406.16 s
408.6 s
~6:48
The signature is consistent and, I think, diagnostic:
The video track and its inline audio track stop at the same instant, while the webcam (renderer-side MediaRecorder) and the cursor monitor (separate process) run the full duration. So it is not the writer failing on one input — it is the whole SCStream going away, taking both .screen and microphone sample buffers with it.
The file is perfectly well-formed.ftyp / mdat / moov, and the indexed packet bytes account for 204,156,468 of the mdat's 204,156,490 — there is no unindexed tail to recover. finishWriting() ran normally on stop, because as far as AVAssetWriter is concerned nothing went wrong; it simply stopped being fed. (This is what distinguishes it from Long recording (70min/5GB) crashes on stop — MP4 missing moov atom AND cursor.json never written, recording unusable #821, where the moov is missing.)
The capture does not degrade into the stop, it stops dead. The last second of the truncated file is a clean 60 fps cadence:
That buffer is inspected exactly once, immediately after waitForNativeCaptureStart, to test for MICROPHONE_CAPTURE_UNAVAILABLE (line 810). After that nothing reads it for the rest of the recording. So the helper reports the failure and no one is listening, which is why the UI carries on as if all is well.
Why the stream is being torn down in the first place
max() makes 60 fps a floor, not a default — a lower config.fps cannot take effect. Full-display capture then sets streamConfig to display bounds × backing scale with no ceiling, so a 2560×1440 Retina display is captured at 5120×2880 @ 60 fps with queueDepth = 6.
The encoder cannot hold that, and the file shows it: in session B, 42.6 s of the 57 s timeline is drop-gaps (gaps up to 4.5 s), 622 frames total — videoInput.isReadyForMoreMediaData is false most of the time and those frames are silently discarded. Sustained back-pressure like that is what precedes the teardown in both of my sessions.
Two things fall out of that:
Window capture is not a workaround, it is heavier. The windowId path still configures the stream for the entire display at full scale, switches to kCVPixelFormatType_32BGRA (vs the 4:2:0 biplanar format used for display capture), and then crops every frame on the CPU in appendCroppedVideoFrame. More bytes per frame plus per-frame work on the shared sample handler queue.
With the fps floor hardcoded, a user whose stream is being starved has no in-app lever at all. They have to change their display resolution in System Settings.
Expected
If the stream dies mid-recording, the app should say so and stop, rather than continue presenting a recording that is no longer being captured. Concretely:
didStopWithError should mark the capture as failed and drive finalization, so the partial file is closed deliberately and the error carries a reason.
The helper should emit a machine-readable line (e.g. STREAM_STOPPED: <error>), matching the existing MICROPHONE_CAPTURE_UNAVAILABLE convention.
The Electron side should keep watching stderr for the whole session rather than sampling it once at start, and surface a stop + notification. A watchdog on frame arrival (no accepted frame for N seconds while unpaused) would also catch silent stalls that never produce a didStopWithError.
Independently: give config.fps real authority (config.fps ?? targetCaptureFPS rather than max(...)), and consider capping capture dimensions for full-display capture so 5K panels do not default to a 885 Mpixel/s stream.
Even just #1 and #2 would convert a silently lost take into an immediate, obvious failure.
Environment
Recordly 1.4.0 (dev.recordly.app), installed from the release DMG
macOS 26.2, Apple silicon
Native ScreenCaptureKit backend, full-display capture of a 27" 5K display (UI looks like 2560×1440), mic on, system audio off, webcam on
Disk had ~4 GB free, and the truncated file is 71 MB — not a disk-space failure
Problem
On macOS, when the
SCStreamstops mid-capture, the recording silently truncates. Nothing in the UI changes: the recording indicator stays on, the webcam keeps recording, the cursor track keeps logging, and the timer keeps counting. You discover minutes later, in the editor, that the screen video ends at a fraction of the take.I lost two takes to this in one session (6:46 recorded → 55 s of video; 8:23 recorded → 115 s of video) and only found the cause by probing the files.
Type
bug
Evidence
Two sessions, full-display capture of a 5K display, mic on, system audio off. Each recording produces four artefacts; here is what each contains:
The signature is consistent and, I think, diagnostic:
MediaRecorder) and the cursor monitor (separate process) run the full duration. So it is not the writer failing on one input — it is the wholeSCStreamgoing away, taking both.screenand microphone sample buffers with it.ftyp/mdat/moov, and the indexed packet bytes account for 204,156,468 of themdat's 204,156,490 — there is no unindexed tail to recover.finishWriting()ran normally on stop, because as far asAVAssetWriteris concerned nothing went wrong; it simply stopped being fed. (This is what distinguishes it from Long recording (70min/5GB) crashes on stop — MP4 missing moov atom AND cursor.json never written, recording unusable #821, where the moov is missing.)Root cause
electron/native/ScreenCaptureKitRecorder.swift:512— the whole handler:It does not clear
isRecording, does not finalize, does not attempt a restart, and does not notify Electron. The error goes to stderr.electron/ipc/register/recording.ts:800— where stderr goes:That buffer is inspected exactly once, immediately after
waitForNativeCaptureStart, to test forMICROPHONE_CAPTURE_UNAVAILABLE(line 810). After that nothing reads it for the rest of the recording. So the helper reports the failure and no one is listening, which is why the UI carries on as if all is well.Why the stream is being torn down in the first place
ScreenCaptureKitRecorder.swift:25,106:max()makes 60 fps a floor, not a default — a lowerconfig.fpscannot take effect. Full-display capture then setsstreamConfigto display bounds × backing scale with no ceiling, so a 2560×1440 Retina display is captured at 5120×2880 @ 60 fps withqueueDepth = 6.The encoder cannot hold that, and the file shows it: in session B, 42.6 s of the 57 s timeline is drop-gaps (gaps up to 4.5 s), 622 frames total —
videoInput.isReadyForMoreMediaDatais false most of the time and those frames are silently discarded. Sustained back-pressure like that is what precedes the teardown in both of my sessions.Two things fall out of that:
windowIdpath still configures the stream for the entire display at full scale, switches tokCVPixelFormatType_32BGRA(vs the 4:2:0 biplanar format used for display capture), and then crops every frame on the CPU inappendCroppedVideoFrame. More bytes per frame plus per-frame work on the shared sample handler queue.Expected
If the stream dies mid-recording, the app should say so and stop, rather than continue presenting a recording that is no longer being captured. Concretely:
didStopWithErrorshould mark the capture as failed and drive finalization, so the partial file is closed deliberately and the error carries a reason.STREAM_STOPPED: <error>), matching the existingMICROPHONE_CAPTURE_UNAVAILABLEconvention.didStopWithError.config.fpsreal authority (config.fps ?? targetCaptureFPSrather thanmax(...)), and consider capping capture dimensions for full-display capture so 5K panels do not default to a 885 Mpixel/s stream.Even just #1 and #2 would convert a silently lost take into an immediate, obvious failure.
Environment
dev.recordly.app), installed from the release DMGUI looks like 2560×1440), mic on, system audio off, webcam onPossibly related
didStopWithErrorpath, but there the stop is at least visible; here it is not.moov. Different failure mode: there the writer never finalizes, here it finalizes cleanly over a short file.