Skip to content

fix(recording): flush every packet to the out-of-process muxer - #2643

Merged
richiemcilroy merged 5 commits into
mainfrom
fix/oop-muxer-flush-packets
Oct 19, 2026
Merged

richiemcilroy merged 5 commits into
mainfrom
fix/oop-muxer-flush-packets

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

With the experimental out-of-process muxer on (Settings > Experimental), encoded screen video sits in the recorder's memory instead of reaching cap-muxer, so the muxer that is meant to make recordings survive crashes loses everything a crash catches in flight, and a muxer crash goes unnoticed until stop.

flowchart LR
    E[encoder thread] -->|write_packet| W["BufWriter 1 MiB<br/>(recorder memory)"]
    W -->|"flushed only at Start / Finish"| P[stdin pipe] --> M[cap-muxer] --> D[(segments)]
    style W fill:#fdd
Loading
  • MuxerSubprocess wraps the child's stdin in a 1 MiB BufWriter (crates/recording/src/output_pipeline/oop_muxer.rs:225) and write_packet never flushes it. At screen bitrates that is seconds of video, and on a mostly static screen far more.
  • A dead child is only noticed when a write fails, which with the buffer happens after 1 MiB, so RespawningMuxerSubprocess never respawned in practice and every packet after the crash went into the dead pipe.
  • write_packet now flushes after every packet (one write per packet, 30 to 60 per second). The buffer still assembles each frame's header and payload into a single write.
match write_packet_frame(stdin, &packet).and_then(|()| Ok(stdin.flush()?)) {

Effect

Engine-verify crash suite on Blacksmith macOS (libx264), out-of-process muxer, synthetic 30 fps screen:

scenario before after
recorder killed after 4 to 6 s (5 kill points) nothing on disk, every frame lost (121 to 184 frames) everything up to the last finished fragment recovered (same as in process)
cap-muxer killed after 3 s no respawn; stop() fails the display track ("subprocess was reaped with unusable output") respawned muxer continues; recording stops cleanly
clean recording 240/240 frames 240/240 frames, identical to the in-process muxer

Respawned muxer kept at its capture time

Once packets reach the child, a killed cap-muxer really is replaced, and that path had never run. Recovery finalizes the first muxer's fragments and each respawn-N directory into separate files, then joined them with the concat demuxer, which plays each file straight after the previous one. The fragment the killed muxer had open is lost, so everything after it moved earlier by that gap: on the crash/muxer-kill/oop case, 144 frames landed 1.2 s early, which would put the rest of the screen track 1.2 s ahead of its audio.

  • The respawned muxer already writes the recorder's own timestamps (its group starts at 3.2 s after a kill at 3 s).
  • concatenate_video_fragments_on_timeline (crates/enc-ffmpeg/src/remux.rs) gives each file a concat duration equal to the gap to the next file's start, so the next file starts at its capture time and the last frame before the gap is held. It never uses less than the file's own duration, so overlapping files still play end to end, and it falls back to the probed duration when a start time is unknown.
  • Only the multi-group join in RecoveryManager::finalize_to_progressive_mp4_with_validation uses it. Single-group and in-process recordings take the same path as before.
crash/muxer-kill/oop, local M4 Max, 3 runs before after
frames recovered 204 of 240 204 of 240
worst timestamp error -1200 ms 0.00 ms

timeline_concat_keeps_a_later_file_at_its_start_time checks a file starting at 1.0 s stays there (the old join puts it before 0.5 s), and timeline_concat_plays_overlapping_files_end_to_end covers overlap. With #2644 merged, every crash/recorder-kill/oop/* and crash/muxer-kill/oop case passes locally, so their xfail entries are removed from crates/verify/expectations.toml.

every_packet_reaches_the_child_before_the_next_one_is_written and a_dead_child_is_reported_on_the_next_packet (oop_muxer.rs tests) fail without the flush. finish_preserves_packets_when_a_slow_muxer_takes_more_than_five_seconds now measures from the packet write, since a muxer that reads nothing for 6 s now blocks the write instead of finish.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new blocking issue was established.

Summary

The PR flushes every encoded packet to cap-muxer and preserves capture-time gaps when joining files from replacement muxers.

  • Each packet reaches the out-of-process muxer as soon as the recorder writes it.
  • Recovery keeps replacement muxer video on the recording timeline.

No new actionable issues were established. Review used the full PR diff because the current and previously reviewed commits have different merge bases with the target commit. Tests were inspected but not run.

Reviews (3) · Last reviewed commit: "fix(recording): keep a respawned muxer's..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cap-status Ready Ready Preview Oct 18, 2026 3:36pm UTC

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

Comment thread crates/recording/tests/oop_muxer.rs
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

@blacksmith-sh

This comment has been minimized.

@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai review

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

@richiemcilroy
richiemcilroy merged commit 8426177 into main Oct 19, 2026
59 of 61 checks passed

This branch was successfully deployed

2 active deployments
Preview – cap-web — f855094b Deployed Oct 18, 2026 by vercel[bot]
Preview – cap-status — f855094b Deployed Oct 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant