Repository navigation
Fix Files find result aggregation - #3818
Conversation
The find-in-folder regression needs a behavior-level assertion that the search controller publishes every match from a folder, not just the first streamed row. This creates a temporary folder with three matching files and asserts the settled snapshot includes exactly those results. Constraint: Issue #3817 requires a failing regression test before the production fix Confidence: high Scope-risk: narrow Directive: Keep this test on the real FileSearchController path so result aggregation regressions fail before UI-only symptoms Tested: git diff --check -- cmuxTests/FileExplorerStoreTests.swift Not-tested: Local XCTest execution skipped because this task forbids direct xcodebuild
The typing-lag refactor made final result publication depend on both ripgrep stdout and stderr stream completion. Stdout is the only stream that defines match completeness; stderr is diagnostic and can otherwise keep the controller on the first throttled snapshot. Drain stderr in a side task, wait for process termination plus stdout drain, then publish the accumulated pipeline results. Constraint: Preserve the valid typing-lag optimization by keeping query debounce and background result parsing intact Rejected: Collecting or limiting results in the view | the controller already owns aggregation and the UI should receive complete snapshots Rejected: Waiting for stderr before final publication | stderr does not define match completeness and can block replacement of the first streamed result Confidence: medium Scope-risk: narrow Directive: FileSearchOutputPipeline stdout is the source of truth for match aggregation; keep stderr diagnostic-only unless failures need a separate bounded contract Tested: git diff --check -- Sources/FileExplorerSearchController.swift cmuxTests/FileExplorerStoreTests.swift Not-tested: Local XCTest execution skipped because this task forbids direct xcodebuild
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughstderr is drained concurrently while stdout is streamed for matches; completion waits on stdout (stderr awaited only on non-success). The view defers content-driven refreshes while searches stream. Tests verify full match publication and that contentRevision reloads don't restart active find searches. ChangesSearch result synchronization fix, UI refresh deferral, and tests
Sequence Diagram(s)sequenceDiagram
participant Client
participant FileSearchController
participant StdoutTask
participant StderrTask
participant Process
Client->>FileSearchController: start search request
FileSearchController->>StdoutTask: stream stdout (matches)
FileSearchController->>StderrTask: start stderr drain (concurrent)
StdoutTask-->>Client: publish matches progressively
Process->>FileSearchController: terminationSignal
FileSearchController->>StdoutTask: await stdoutDone
alt non-success exit
FileSearchController->>StderrTask: await stderrTask.value
else success or code 1
FileSearchController->>StderrTask: cancel/ignore stderr
end
FileSearchController->>FileSearchController: pipeline.finish(status)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (3 errors, 1 warning)
✅ Passed checks (11 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR fixes the Files Find result aggregation regression from #3739. It switches stdout handling from
Confidence Score: 4/5The I/O overhaul correctly fixes the aggregation bug and the previously-flagged trailing-line .limited issue; one minor deferred-refresh flag gap remains in direct refreshSearchIfNeeded() call sites. The pipe-read rewrite, buffer framing, and terminalUpdate/trailingUpdate flow in finish() are all correctly implemented. The only remaining gap is refreshSearchIfNeeded() not clearing pendingSearchRefreshAfterSettled, which can cause an extra harmless re-search via focusSearchField() — not a correctness break, but a visible UX flicker in a narrow path. Sources/FileExplorerView.swift — specifically refreshSearchIfNeeded() and its interaction with pendingSearchRefreshAfterSettled in paths that bypass scheduleSearchRefresh(). Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as FileExplorerContainerView
participant SC as FileSearchController
participant OT as searchTask (outer Task.detached)
participant ST as stdoutTask (Task.detached)
participant ER as stderrTask (Task.detached)
participant PL as FileSearchOutputPipeline (actor)
participant PR as FileSearchPipeReader (DispatchQueue)
UI->>SC: search(query:rootPath:)
SC->>SC: spawn Process (rg --json)
SC->>OT: Task.detached
OT->>ST: Task.detached streamStdout
OT->>ER: Task.detached streamStderr (utility)
OT->>OT: await terminationSignal.wait()
loop Read 32 KB chunks
ST->>PR: FileSearchPipeReader.read()
PR-->>ST: .chunk(data)
ST->>PL: consumeStdout(data)
PL-->>ST: update? (throttled by snapshotInterval)
ST->>SC: applyPipelineUpdate(update, generation)
end
Note over ST: EOF from rg stdout
ST-->>OT: stdoutTask completes
Note over OT: rg exits, terminationSignal fires
OT->>OT: await stdoutTask.value
OT->>PL: finish(status)
PL->>PL: flush stdoutBuffer trailing line
PL-->>OT: terminalUpdate ?? trailingUpdate ?? normal
OT->>SC: finish(generation:update:)
SC->>UI: onSnapshotChanged(.matches / .limited / .noMatches)
Reviews (8): Last reviewed commit: "Avoid async XCTest unwrap autoclosure" | Re-trigger Greptile |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 33b7cfd. Configure here.
Review feedback correctly noted that successful searches do not need stderr for result completeness, but failed ripgrep exits still use stderr to format the visible error. Keep stdout as the success/no-match completion gate and wait for the diagnostic stream only on non-0/1 statuses before finishing the pipeline. Constraint: Preserve complete successful result publication without regressing failed-search diagnostics Rejected: Await stderr for every search | reintroduces the one-result regression by letting diagnostic stream completion gate successful results Confidence: high Scope-risk: narrow Directive: Only failure statuses may wait on stderr; stdout remains the match-completeness stream Tested: git diff --check -- Sources/FileExplorerSearchController.swift Not-tested: Local XCTest execution skipped because this task forbids direct xcodebuild
|
Reviewed the latest Greptile medium-classified summary: it marks this as safe to merge and does not request a code change. The noted unstructured stderr task is intentionally bounded by the surrounding search task via defer cancellation; the only awaited stderr path runs after rg has exited and is needed to preserve failure diagnostics. |
A content revision from the Files sidebar can arrive while find-in-folder is still streaming results. The regression test pins that a same-query content refresh is queued instead of immediately issuing another search request and clearing the active stream. Constraint: Local xcodebuild is prohibited in this workspace; validation is limited to diff checks before CI. Confidence: high Scope-risk: narrow Tested: git diff --check -- cmuxTests/FileExplorerStoreTests.swift Not-tested: XCTest execution, deferred to CI per repository policy
Find-in-folder was still showing one live result because file-tree content revisions and same-mode sidebar updates could reissue the same query while ripgrep was streaming. That cancelled the current process, cleared accumulated results, and restarted before the stream reached the rest of stdout. This keeps query/root/provider changes immediate, but queues same-query content refreshes until the active search settles. The result stream can now accumulate normally, and file changes still trigger one follow-up search after the current pass. Constraint: Preserve typing-lag optimization by debouncing query input and not debouncing result output. Rejected: Remove contentRevision refresh entirely | would miss file changes after an existing query. Confidence: high Scope-risk: narrow Tested: git diff --check -- Sources/FileExplorerView.swift cmuxTests/FileExplorerStoreTests.swift Not-tested: XCTest execution, deferred to CI per repository policy
|
Follow-up for the live repro screenshot: the one-result behavior was caused by same-query Find searches being restarted by Files sidebar content revisions while rg was still streaming. Added c02aa84 to cover that starvation path and dd25a5d to queue content-refresh reruns until the active search settles, so query/root changes still refresh immediately but result streaming is no longer cancelled by tree reloads. |
The new Files find lifecycle regression test lives in FileSearchControllerTests, so it needs its own condition-based wait helper instead of calling the FileExplorerStoreTests helper from another XCTest class. This keeps the regression coverage executable in CI without changing the production fix. Constraint: Local direct xcodebuild is forbidden for this task; CircleCI provided the failing compile diagnostic. Rejected: Use a fixed sleep | the test should wait for the observable search request count. Confidence: high Scope-risk: narrow Tested: git diff --check -- cmuxTests/FileExplorerStoreTests.swift Sources/FileExplorerView.swift Not-tested: Local unit suite, per repository/task policy.
The live dev build showed ripgrep still running with its stdout pipe full while the Find UI stayed at one match. This regression test exercises the behavior-level contract for high-volume folder searches: the controller must drain enough stdout to publish the 500-result limit instead of waiting for ripgrep to finish naturally. Constraint: Local direct xcodebuild is forbidden for this task; the test is committed for CI to exercise. Confidence: medium Scope-risk: narrow Tested: git diff --check -- cmuxTests/FileExplorerStoreTests.swift Not-tested: Local unit suite, per repository/task policy.
The second dogfood repro showed the real failure mode: the app had a live ripgrep child with stdout pipe full while the Find UI stayed at one match. The async line reader introduced by the typing-lag optimization could stop draining stdout, leaving ripgrep blocked and the search permanently in the streaming state. This keeps the typing-lag intent by parsing stdout off the main actor and publishing throttled snapshots through the existing pipeline, but it no longer depends on FileHandle.AsyncBytes.lines. A detached POSIX-read pump drains stdout as bytes, the actor owns line framing and result accumulation, and the controller still cancels only on query/scope changes or the 500-result limit. Constraint: Build must use scripts/reload.sh only; no direct xcodebuild. Rejected: Revert to the old MainActor readabilityHandler | it fixed draining but reintroduced the typing-lag path. Rejected: Raise or collect the result limit | the bug is stdout starvation, not the limit size. Confidence: high Scope-risk: moderate Directive: Keep query debounce separate from stdout draining; result aggregation must continue even when UI snapshot publishing is throttled. Tested: git diff --check -- Sources/FileExplorerSearchController.swift cmuxTests/FileExplorerStoreTests.swift Not-tested: Local unit suite, per repository/task policy.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/FileExplorerSearchController.swift`:
- Around line 363-397: The detached read tasks capture raw Int32 fds
(stdout.fileHandleForReading.fileDescriptor /
stderr.fileHandleForReading.fileDescriptor) without retaining the
Pipe/FileHandle, leading to fd reuse races; fix by retaining the read FileHandle
(or the Pipe) in the Task capture list so the closure keeps the read end alive
for the lifetime of streamStdout/streamStderr — e.g. capture
stdout.fileHandleForReading and stderr.fileHandleForReading (or the Pipe
instances) in the Task.detached closures used by searchTask and pass those
FileHandle objects into Self.streamStdout and Self.streamStderr instead of raw
Int32 fds, ensuring terminationSignal, process, searchGeneration, and pipeline
logic remain unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 99892fdb-6523-4ba5-962a-af667d3654a2
📒 Files selected for processing (2)
Sources/FileExplorerSearchController.swiftcmuxTests/FileExplorerStoreTests.swift
Merged origin/main into the PR branch before processing review feedback and checks so CI validates against the latest shared code. Constraint: iterate-pr workflow requires syncing the PR branch with its base before feedback and CI work Confidence: high Scope-risk: narrow Reversibility: clean Tested: Merge completed cleanly with ort strategy Not-tested: Local tests not run per repo testing policy
Detached search stream tasks now retain read handles for stdout and stderr while blocking reads run on a dedicated Dispatch queue instead of Swift's cooperative executor. The search pipeline also preserves a final limited update when the last buffered stdout line reaches the result cap. Constraint: Review bots flagged fd reuse risk, cooperative executor blocking, and final trailing-line limit misreporting Rejected: Returning to FileHandle.bytes.lines | prior PR changed away from line iteration to avoid result-completeness coupling with stderr/process exit timing Confidence: medium Scope-risk: moderate Reversibility: clean Directive: Keep file search completion defined by stdout EOF; stderr is diagnostic-only except non-0/1 exits Tested: git diff --check; tagged reload queued for compile verification Not-tested: Local unit tests not run per repo testing policy
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
The find panel already has the search-field placeholder, so the idle status line repeated the same instruction and added visual noise before the user has typed anything. Constraint: Keep non-idle search status text unchanged Confidence: high Scope-risk: narrow Tested: ./scripts/reload.sh --tag issue-3817-find-in-folder-one-result Not-tested: Local test suite per repository policy; CI will run after push
The search pipeline can hit the result cap before the outer process task finishes. Remembering the terminal limited update makes finish(status:) return the same terminal state instead of relying on the controller generation guard to discard later completion updates. Constraint: Keep stdout-gated finalization and early process-stop behavior unchanged Rejected: Only document the generation-guard invariant | review feedback identified this as a future refactor hazard Confidence: high Scope-risk: narrow Tested: ./scripts/reload.sh --tag issue-3817-find-in-folder-one-result Not-tested: Local XCTest execution per repository policy; CI will run after push
The pipeline limit regression test awaited inside XCTUnwrap's synchronous autoclosure, which can fail test compilation even though the app target builds. Split the awaited optional from the assertion so XCTest sees a plain value. Constraint: Local XCTest execution is disabled by repository policy Confidence: medium Scope-risk: narrow Tested: git diff --check -- cmuxTests/FileSearchRipgrepParserTests.swift Tested: ./scripts/reload.sh --tag issue-3817-find-in-folder-one-result Not-tested: Local XCTest execution per repository policy; CircleCI macOS unit tests will verify
Dismissed as stale: the CodeRabbit fd-retention finding was addressed by retaining read handles, the inline thread is resolved, and the latest CodeRabbit check is passing on 741ff5a.
Summary
Regressing Commit
Regression from
52db9e358f9073526de98c10ba424275751c06fe(Fix right sidebar Find typing lag (#3739)).Testing
./scripts/reload.sh --tag repro-3817-main --launch.debug.right_sidebar.focusreturned visible/focused true). Local AX/screen-capture access was unavailable, so direct visual row-count capture could not be collected from this environment.git diff --check -- cmuxTests/FileExplorerStoreTests.swiftgit diff --check -- Sources/FileExplorerSearchController.swift cmuxTests/FileExplorerStoreTests.swiftNot Tested
Checklist
./scripts/reload.sh --tag issue-3817-find-in-folder-one-result.origin/main.Fixes #3817
Note
Medium Risk
Touches the ripgrep process I/O/concurrency path and search refresh scheduling; mistakes could cause hangs, missed results, or extra process churn, but changes are localized and covered by new tests.
Overview
Fixes Files Find result aggregation by switching ripgrep output handling to chunked POSIX pipe reads with stdout line buffering/flush-on-finish, preserving the terminal
.limitedupdate and treating stderr as diagnostic-only (only awaited on error exits).Adjusts Find refresh behavior so root/provider scope changes still refresh immediately, but content revision changes during an active search are deferred until the current stream settles; also removes the idle “Type to search” prompt. Adds regression tests for multi-file/nested match completeness, early limiting to 500 results, pipeline finish behavior with buffered trailing lines, and avoiding mid-stream restarts on content revision changes.
Reviewed by Cursor Bugbot for commit 741ff5a. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Fixes Files Find to publish all matches and prevent stalls or lost results during folder searches and content refreshes. Completion is now gated on ripgrep stdout, drained independently; same-query content refreshes run after the current search settles, and the terminal limited state is preserved on finish.
XCTUnwrap. Remove the redundant idle Find prompt. Fixes Find-in-folder only shows one result (regression from typing-lag optimization) #3817.Written for commit 741ff5a. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes
Behavior
Tests