Skip to content

[Bug]: macOS — the writer dies 75 s into a take, ScreenCaptureKit keeps delivering frames for 22 more minutes, and the HUD keeps counting #621

Description

@makewheels

Search existing issues

  • I have searched the existing issues

Describe the bug

A take that ran 23 min 20 s produced a 74-second file.

The write path died about 75 seconds in. ScreenCaptureKit kept capturing and kept delivering healthy frames for another 22 minutes. The HUD kept counting up the whole time. Nothing told the user the recording had effectively already ended — the loss was completely silent and was only discoverable afterwards, from the filesystem and the unified log.

I believe v1.10.0 is affected too: nothing in that release or on main since looks like it addresses this, and #363 explicitly scoped macOS out.

Environment: OpenScreen 1.9.6 (com.etiennelescot.openscreen), macOS 26.6.2 (25G83), Apple M4, arm64. Screen + USB_MIC capture, 1920×1080, no camera.

Note on timestamps. Absolute dates and clock times are redacted throughout. Times are given as offsets from the take's start (T0). In the quoted log lines only the leading timestamp has been substituted with its T0 offset — everything after it is verbatim from log show --predicate 'process == "replayd"'. Filenames are redacted where they embed a timestamp (see §1).

1. The output file stops at 74 s but is not truncated

The take's recording-<redacted>.mp4 under ~/Library/Application Support/openscreen/recordings/. The numeric suffix is redacted because it is an epoch-milliseconds start timestamp, which is how T0 is known:

  • fragmented MP4: ftyp, wide, moov at offset 1410192, then 73 moof and 75 mdat boxes — roughly one fragment per second of the 74 s take
  • the box lengths sum to exactly the file size (74953463 bytes) → the writer stopped cleanly at a fragment boundary; this is not a partial or torn write
  • ffprobe: duration 74.0 s, 1920×1080, h264 r_frame_rate=54, + aac 48 kHz stereo
  • ffmpeg -v warning -i f -f null - → exit 0, zero errors
  • ffprobe -count_frames3998 frames = 54.0 fps across 74 s, consistent end to end
  • mtime froze at T0+75 s and never advanced again
  • there is no .session.json beside it → the save path never ran for this take. The immediately preceding take has both a .session.json and a project file, so this is specific to the failed take, not to the build.

2. replayd reports the session healthy for the entire 23 minutes

266 Health: heartbeats between T0+71 s and T0+23:16, one every ~5 s, all from the same session:

  • screenframeCount 251–297 per 5 s window (≈50–59 fps) throughout — no gap, no degradation
  • audioFrameCount 245–255 per window throughout
  • _screenTimeDriftSeconds=0.00000000000000000000 on all 266

First heartbeat after the file stopped growing (T0+71 s):

[T0+71s] Df replayd[1177:d86ca6]  [INFO] -[SCCaptureSession setupHealthMonitor]_block_invoke:1197 Health: captureSession=0x883624000 streamID=<private> screenframeCount=278 _audioFrameCount=245 _screenTimeSeconds=1369618.893588 _audioTimeSeconds=1369618.826602 _screenTimeDriftSeconds=0.00000000000000000000 compositeSessionMode=0 cameraframeCount=0 compositeFrameCount=0

Last one, 22 minutes later (T0+23:16):

[T0+23:16] Df replayd[1177:d87a2a]  [INFO] -[SCCaptureSession setupHealthMonitor]_block_invoke:1197 Health: captureSession=0x883624000 streamID=<private> screenframeCount=280 _audioFrameCount=255 _screenTimeSeconds=1370943.993019 _audioTimeSeconds=1370943.926603 _screenTimeDriftSeconds=0.00000000000000000000 compositeSessionMode=0 cameraframeCount=0 compositeFrameCount=0

3. The session was only torn down when the client process exited

At T0+23:20, four seconds after the last heartbeat:

[T0+23:20] Df replayd[1177:d87a2a]  [INFO] -[RPClient stop:streamID=<redacted>] Success stopping stream
[T0+23:20] Df replayd[1177:d87a2a]  [INFO] -[SCScreenCaptureSession dealloc] self=0x8823b86e0
[T0+23:20] Df replayd[1177:d87a2a]  [INFO] -[SCCaptureSession dealloc] session=0x883624000
[T0+23:20] Df replayd[1177:d87a2a] [com.apple.ReplayKit:RPDaemon] -[AVCaptureDeviceInput dealloc] <AVCaptureDeviceInput: 0x881a1c720 [USB_MIC]>
[T0+23:20] Df replayd[1177:d87a2a] [com.apple.xpc:connection] [0x8830fca80] invalidated because the client process (pid 54137) either cancelled the connection or exited

The producer side was alive and healthy from T0 to T0+23:20. The consumer stopped persisting at T0+75 s.

4. Ruled out: this was not storage- or OS-level loss

I checked every avenue by which the missing 22 minutes could still have existed somewhere:

  • no file larger than 1 MB written anywhere on the data volume between T0+75 s and T0+23:20 (only unrelated third-party apps)
  • /private/var/vm/ contains no swapfiles at all — nothing was paged out
  • no core dumps in /cores/
  • tmutil listlocalsnapshots /no local APFS snapshots, so no filesystem-level restore point
  • lsof +L1no deleted-but-still-open handles, so nothing was readable off a live fd
  • the recordings/ directory's own mtime never advanced after the take began → no file was created there and later removed

So the frames produced after T0+75 s were delivered to the app and dropped. They were never written to any medium. This is not a data-recovery problem; it is a silent-failure problem.

Why this is not #252 / #292 / #327

Those three are Windows, and the symptom is a hang on stop ("still hangs on stop", "timed out waiting for native windows capture to stop", "video is not saving").

This is macOS, and the failure is mid-recording and silent: capture stays healthy, the writer dies, and the UI keeps counting for 22 minutes. The user gets no signal that the take ended. I could not find a macOS report for this, which is why I am filing one.

Relation to #338 and #363

#338 is the only reason any bytes survived — thank you. Without fragmented MP4 this take would have been an unreadable 0-second file instead of a clean, fully decodable 74-second one. But #338 says of itself: "What it does not change: the freeze. The user still sees a recording that stops on its own. That stays open." This is that open case, on macOS, at 23 minutes.

#363 scoped macOS out: "macOS fragments too and needs the same treatment, but it has no already-exited fast path and an unguarded stdin write, so it is its own change." It also listed under Deliberately not in this PR: "Detecting the mid-recording exit / stopping the HUD timer. The HUD does keep counting past a dead helper (observed: 02:12)." The observation there was 2 min 12 s; mine is 23 min 20 s. This report is the macOS evidence for both halves of that deferral — the salvage path and the dead-helper detection.

Impact

22 of 23 minutes of an unreproducible recording were lost. Because the HUD looked healthy throughout, there was no opportunity to notice and restart.

Expected behavior

One of:

  1. the writer recovers and the take is complete; or
  2. capture is stopped as soon as the writer dies, and the user is told the take ended — with whatever was salvaged opened in the editor rather than discarded (the fix(recording): open the recording a failed stop left playable #363 fall-through, applied to macOS).

What should not happen is 22 minutes of HUD counting over a dead writer with no user-visible signal, followed by a take that silently does not exist.

Ideally the two outcomes are separable in the field: #363 notes the helper already emits a container field nobody read, which is what makes "was this file supposed to survive a kill?" answerable. A recovered: true flag on macOS would let a bug report distinguish a clean stop from a salvaged one here too.

To Reproduce

I cannot reproduce this on demand — it happened once, in a single 23-minute take, and I have no reliable trigger. This report is a forensic reconstruction from the artefacts that survived, not a reproduction.

What the artefacts pin down is the failure shape, which should be enough to look for it directly:

  1. Start a macOS screen + microphone recording.
  2. Make the write path fail while capture continues (whatever fix(recording): write fragmented MP4 so a killed helper keeps its file #338 was defending against — the helper's writer dying rather than being killed).
  3. Observe: the .mp4 stops growing, replayd keeps logging healthy Health: heartbeats at ~55 fps, the HUD keeps counting, and no .session.json is ever written.

The cheap instrumentation to confirm it in the field: compare the output file's mtime against elapsed recording time. A file whose mtime is minutes behind the HUD is a dead writer, and today nothing performs that comparison.

OS

macOS

OS Version

26.6.2 (build 25G83)

Device Type

Desktop

Other Device

Mac mini (Apple M4, arm64)

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions