Skip to content

fix(recording): ignore empty orphan segment directories during recovery - #2412

Open
iwillwin-wcy wants to merge 2 commits into
CapSoftware:mainfrom
iwillwin-wcy:fix/recovery-ignores-empty-orphan-segment
Open

iwillwin-wcy wants to merge 2 commits into
CapSoftware:mainfrom
iwillwin-wcy:fix/recovery-ignores-empty-orphan-segment

Conversation

@iwillwin-wcy

@iwillwin-wcy iwillwin-wcy commented Oct 2, 2026 •

Copy link
Copy Markdown

What

A resume that fails after it has allocated the segment directory leaves an empty segment-N directory behind. That empty directory then makes recovery reject the entire recording:

Incomplete Recording — Recovery validation failed; original recording preserved: Unrecoverable display segment 2

The segments that do hold footage are complete and intact. One empty directory costs the whole recording.

Why

SegmentPipelineFactory::create_next allocates the segment directory, then can fail while resolving the capture target:

INFO  segment{index=2}: cap_d3d_adapter: Selected DXGI adapter ...
WARN  Studio resume failed; retaining paused recording
      error=target_display_crop: Display not found

That path returns the error without removing the directory it just created. discard_resume_pipeline, which does clean up, is only reached from the Linux cancel_resume path.

Recovery then contradicts itself. analyze_incomplete deliberately skips a segment that has no display fragments:

if display_fragments.is_empty() {
    debug!("No display fragments found for segment {}", index);
    continue;
}

but require_recoverable_tracks walked the same directory listing and rejected any index the collector had not collected:

&& !indexes.contains(&index)

So the very segment the collector had just skipped became the reason to abandon everything.

Fix

  • Extract segment_display_fragments and call it from both collection and validation, so the two can no longer drift apart about which segments are recoverable.
  • Validation ignores an orphan directory only when it holds no recoverable display media.
  • A directory that does hold footage but was not collected stays fatal, because recovering around it would silently drop that footage from the recording.

Tests

Two regression tests in crates/recording/src/recovery.rs:

  • empty_orphan_segment_directory_does_not_block_recovery — reproduces the real layout (one segment with encoded fragments plus an empty segment-1) and asserts require_recoverable_tracks succeeds. Fails with Unrecoverable display segment 1 before this change.
  • segment_directory_with_footage_outside_the_recoverable_set_is_still_fatal — pins the guard so the fix stays narrow.

Context

This came out of a real 8m48s recording that was given up for lost: the user paused, minimized the captured window, hit resume, got Display not found, then pressed stop and the whole recording was written off. The footage was complete on disk — 23791 frames — with only this empty directory standing in the way.

Worth fixing rather than just documenting because the trigger is easy to hit in normal use: is_window_valid_for_enumeration filters out any window where IsIconic is true, so minimizing the captured window during a pause is enough.

Verification

rustfmt --edition 2024 --check passes on the changed file. I could not run the tests locally — my machine has no FFmpeg/vcpkg toolchain, so CI is the first compile. Flagging it so a CI failure isn't a surprise.

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no outstanding findings remain.

Findings

  1. P1 Other footage can be omitted ▶
  2. P1 Guard test fails early ▶
Fix with agent prompt
### Issue 1
crates/recording/src/recovery.rs:1353-1356
If a new segment has recoverable microphone, system-audio, or camera media but no decodable display fragment, this check treats it as an empty orphan. Recovery then leaves the segment out of the published metadata, silently omitting that footage. The skip should distinguish an empty directory from one containing other media.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
crates/recording/src/recovery.rs:undefined-5354
The test declares two segments in metadata, then removes segment 1 from the recoverable set. Validation therefore returns `Missing display segment 1` before it checks the populated directory, so the assertion expecting `Unrecoverable display segment 1` fails. Declaring only segment 0 lets the test reach the guard it intends to cover.

```suggestion
        write_recording_meta(project, 1);
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR lets recovery ignore an orphan segment directory only when it contains no recoverable display, camera, microphone, or system-audio media. The revised tests cover empty orphans and populated directories that must remain fatal.

Reviews (2) · Last reviewed commit: "fix(recording): widen the orphan check t..." · Reviewed by Greptile

An interrupted Studio resume can leave a segment directory behind that holds
no media at all: `SegmentPipelineFactory::create_next` allocates the
directory, and can then fail while resolving the capture target, e.g.

    Studio resume failed; retaining paused recording
    error=target_display_crop: Display not found

The failure path returns the error without removing the directory it created.

`require_recoverable_tracks` then iterated every `segment-N` directory on
disk and rejected any index that `analyze_incomplete` had not collected.
An empty directory is deliberately skipped during collection ("No display
fragments found for segment N", logged at debug level), so the two sides
disagreed and the entire recording was reported as

    Unrecoverable display segment N

even though the segments that did hold footage were complete and otherwise
recoverable. One empty directory could cost the whole recording.

Share the display-media probe between collection and validation
(`segment_display_fragments`) so the two can no longer drift apart, and have
validation ignore an orphan directory only when it holds no recoverable
display media. A directory that still has footage but was not collected
stays fatal, because recovering around it would silently drop that footage.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread crates/recording/src/recovery.rs Outdated
Comment on lines 1353 to 1356
if Self::segment_display_fragments(&entry.path())
.fragments
.is_empty()
{

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.

P1 Other footage can be omitted If a new segment has recoverable microphone, system-audio, or camera media but no decodable display fragment, this check treats it as an empty orphan. Recovery then leaves the segment out of the published metadata, silently omitting that footage. The skip should distinguish an empty directory from one containing other media.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/recovery.rs
Line: 1353-1356

Comment:
**Other footage can be omitted** If a new segment has recoverable microphone, system-audio, or camera media but no decodable display fragment, this check treats it as an empty orphan. Recovery then leaves the segment out of the published metadata, silently omitting that footage. The skip should distinguish an empty directory from one containing other media.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread crates/recording/src/recovery.rs Outdated

write_fragmented_display(&project.join("content/segments/segment-0"));
write_fragmented_display(&project.join("content/segments/segment-1"));
write_recording_meta(project, 2);

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.

P1 Guard test fails early The test declares two segments in metadata, then removes segment 1 from the recoverable set. Validation therefore returns Missing display segment 1 before it checks the populated directory, so the assertion expecting Unrecoverable display segment 1 fails. Declaring only segment 0 lets the test reach the guard it intends to cover.

Suggested change
write_recording_meta(project, 2);
write_recording_meta(project, 1);
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/recovery.rs
Line: 5354

Comment:
**Guard test fails early** The test declares two segments in metadata, then removes segment 1 from the recoverable set. Validation therefore returns `Missing display segment 1` before it checks the populated directory, so the assertion expecting `Unrecoverable display segment 1` fails. Declaring only segment 0 lets the test reach the guard it intends to cover.

```suggestion
        write_recording_meta(project, 1);
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks for the detailed write-up, this is a real bug. Before review we need Greptile at 5/5 and green CI. Please only skip a segment directory when it holds no recoverable media at all (not just no display), fix the guard test so it actually reaches the populated-directory check, then request a Greptile re-review.

…rd test

Greptile P1: the skip only looked at display media, so a segment directory
holding recoverable microphone, system-audio or camera media but no decodable
display fragment was treated as an empty orphan. Recovery then left it out of
the published metadata and silently omitted that footage. Skip a directory only
when it holds no recoverable media of any kind.

`segment_display_fragments` grows into `segment_track_fragments`, which the
camera arm of segment collection now shares as well, and
`segment_holds_recoverable_media` composes it with the audio probe.

Greptile P1: the guard test declared two segments in metadata and then dropped
segment 1 from the recoverable set, so validation returned "Missing display
segment 1" before reaching the directory-level check it was meant to cover and
its assertion could never pass. Declare a single segment so the guard is
actually reached, and add a companion test for a directory holding only a
non-display track.

Co-Authored-By: Claude <noreply@anthropic.com>
@iwillwin-wcy

Copy link
Copy Markdown
Author

@greptileai

Both P1s are addressed in 615e467:

Skip condition widened to every track. segment_display_fragments grew into segment_track_fragments(segment_path, track, single_file), which the camera arm of segment collection now shares too, and segment_holds_recoverable_media composes it with the audio probe. A segment directory is skipped only when it holds no recoverable media of any kind — one holding only microphone, system-audio or camera media stays fatal, because skipping it would leave that footage out of the published metadata.

Guard test now reaches the guard. It declared two segments in metadata and then dropped segment 1 from the recoverable set, so validation returned Missing display segment 1 before ever reaching the directory-level check, and the assertion could not pass. It now declares a single segment. Added a companion test (segment_directory_holding_only_non_display_media_is_still_fatal) covering the widened condition with a camera-only directory.

This branch has not been deployed

No deployments
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