Skip to content

Bound file-search inventories and release abandoned Markdown data - #25369

Merged
nwparker merged 18 commits into
mainfrom
nwparker/file-search-memory-bounds
Oct 6, 2026
Merged

nwparker merged 18 commits into
mainfrom
nwparker/file-search-memory-bounds

Conversation

@nwparker

@nwparker nwparker commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 33 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1797 $\color{#cf222e}{\Huge{\mathbf{−}}}$​148 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1649
Prod 45 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1493 $\color{#cf222e}{\Huge{\mathbf{−}}}$​609 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​884

ELI5

Part 1 of 3; base main, followed by #25370 and #25371.

Large listings fail clearly at an explicit memory limit. Markdown links and completion find documents beyond the ordinary file-list page.

What Changed

Accounts for directory entries, transfer plans, inventory retention, response queues and pending connections while data arrives. Uses a filtered Markdown producer on current hosts and the existing bounded full-inventory path on older hosts.

Preserves Windows drive/UNC path identity and historical SSH hidden-directory, ignore-file, unreadable-subdirectory and missing-ripgrep behavior. WSL failure and cancellation use the existing process-tree terminator.

Why

Producer-side accounting avoids allocating an oversized list before rejecting it. Reusing existing discovery and process ownership preserves established host behavior and prevents a second fallback policy.

Linked Issue

Fixes #17592.

Visual Proof

Markdown link activation previously remained on Source; the corrected path opens Target. Completion insertion and loaded content were asserted in the hidden app.

Before: link activation stays on Source

Before: link activation stays on Source

After: Target document opens

After: Target document opens

Testing

  • Automated regressions added and verified.
  • Hidden rendered-app and real host checks completed.

Bounds-layer pnpm tc and six focused suites (50 tests) passed. Combined-stack verification passed 88 suites (888 tests, one existing skip), full typecheck and changed-code quality checks. Real ripgrep regressions cover Windows path identity, SSH ignore policy, a 25,002-source-file tree, missing binaries and unreadable subdirectories.

Real Windows/Ubuntu 24.04 WSL probes passed abort, timeout and invalid-output cleanup. macOS rendered Markdown navigation passed four flows; current/older production SSH handlers passed nine client/host pairings including a complete 23,000-file inventory.

Two hidden native Windows Electron checks also passed on final head e44c29d019e0: linked-folder refresh through retargeting and Quick Open rendered filename/path results.

All tests/apps used ORCA_BACKGROUND_LAUNCH=1. Fresh hosted CI on the rewritten heads is queued; merge remains gated on required checks.

Review

A fresh independent reviewer rated the final combined source e44c29d019e0 low risk, with no unresolved concrete blocker after adversarial review, compatibility repairs, real SSH latency checks and native WSL cleanup evidence. Their focused stable-source rerun passed 20 suites / 155 tests. Counts above overlap and are not summed.

The final production TypeScript is 58 net lines smaller than the previous candidate; additional tests preserve the behavior that the simplification must keep.

Agent skill upstream boundary

  • Not applicable.

Notes

Legacy complete inventories retain a 64 MiB admission limit; Markdown discovery retains 20,000 documents / 8 MiB metadata. Oversized data produces an explicit error. These are retained-data limits, not total process RSS guarantees.

Checklist

  • User-facing before/after, mechanism and approach explained
  • Existing before/after visual proof preserved
  • Independent correctness, ownership and performance review
  • Cross-platform, folder, SSH and mixed-version behavior checked
  • Local typecheck, affected tests, fresh app/CLI build and changed-code gates
  • Fresh required hosted CI complete

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

File and directory listings now enforce retention limits, and SSH directory reads and transfers use bounded, cancellable enumeration. Markdown discovery uses shared collection logic and dedicated local, relay, and SSH routes. SSH response streams and Git grep records have capacity limits. Renderer Markdown snapshots and completions are scoped to the active workspace and Monaco model.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to a4f28

The PR adds limits to file and Markdown listings and SSH operations. The remaining issues are narrow edge cases: inconsistent Windows paths for aliased folders, possible capacity errors on bounded Git listings, budget accounting after duplicate names, and duplicate error toasts. It is mergeable with owner awareness or quick follow-ups.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to a4f28

Individual listings gain useful memory limits and failure cleanup. However, the new remote Markdown discovery path bypasses the existing coordination that prevents overlapping whole-workspace scans. Switching workspaces can therefore leave several bounded scans running together, potentially degrading other operations on the same remote connection.

Retained concerns

  • Medium · reliability · inferred: The new native SSH Markdown route bypasses the existing per-client scan coordinator. Different-root requests and fresh refreshes can launch overlapping producers, while renderer abandonment only suppresses stale results rather than stopping the work. Individual capacity limits and timeouts do not preserve the former one-scan-per-client containment guarantee; cumulative work may impair unrelated filesystem operations sharing the relay and connection.
Security review details

Security Blast Radius

  • inferred — The supported concern concerns availability of the executing relay and its shared connection resources. It requires access to the existing filesystem request boundary or overlapping requests from the desktop caller. The evidence does not establish cross-tenant exposure, credential access, or privilege escalation.

Security Findings and Attack Paths

  • inferred — Repeated requests for different roots can reach separate ripgrep producers instead of superseding the previous client's scan. Same-key renderer deduplication and individual timeouts reduce this exposure, but do not establish aggregate scan containment. Resource exhaustion was not demonstrated.

Trust Boundaries and Controls

  • observed — The relay's host-path authority predates this PR: existing listing routes accept expanded caller-supplied roots through the same handler boundary. The new Markdown route adds a string check and uses a fixed producer command; no new filesystem identity or executable-selection authority was found in this path.

Resilience and Maintainability Implications

  • observed — SSH response handling isolates streams by stream identity and cleans request-owned buffers, subscriptions, and timers on terminal paths. Multiplexer abort and timeout send cancellation to the relay; session teardown disposes the multiplexer before unregistering the filesystem provider.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Git grep record cap and its tests implement [#17592]. The PR also adds file and directory inventory limits, SSH listing and transfer changes, Markdown discovery and navigation changes, and editor … Move the unrelated inventory, SSH, Markdown, and editor-completion changes and their tests to separately scoped pull requests. Keep the Git grep record cap and its required error handling and regression tests in this PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.41% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 54 functions across 62 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: bounding file-search inventories and releasing abandoned Markdown data.
Description check ✅ Passed The description covers the required sections, links issue #17592, explains the change and rationale, includes visual proof, and reports testing. It also states that required hosted CI is still pending…
Linked Issues check ✅ Passed [ #17592 ] requires an 8 MiB bound for newline-free Git grep records and search-process regression tests. searchWithGitGrep sets SearchSubprocessLineAccumulator to GIT_GREP_MAX_RECORD_BYTES and …
Full details: Out of Scope Changes check

Explanation

The Git grep record cap and its tests implement [#17592]. The PR also adds file and directory inventory limits, SSH listing and transfer changes, Markdown discovery and navigation changes, and editor completion retention. These changes do not implement or test the Git grep record cap.

Full details: Docstring Coverage

Explanation

Docstring coverage is 7.41% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 54 functions across 62 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/renderer/src/components/editor/useMarkdownDocuments.ts (1)

100-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale snapshot can remain visible when the scope has no path.

refreshMarkdownDocuments returns early when worktreeId or worktreePath is missing. The effect cleanup still increments requestRef. The snapshot key then no longer matches scopeKey, so the hook returns an empty list. This behavior is correct.

The error path has a small redundancy. Two separate if (requestRef.current === requestId) checks guard the toast and the setSnapshot call. Merge them into one block.

♻️ Proposed merge
         if (requestRef.current === requestId) {
           toast.error(err instanceof Error ? err.message : 'Failed to list Markdown documents.')
-        }
-        if (requestRef.current === requestId) {
           setSnapshot({ key: scopeKey, documents: [] })
         }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 054bbba0-463e-41db-885c-7bd43a0f9b94
📥 Commits

Reviewing files that changed from the base of the PR and between d9846e6 and 7910655.

📒 Files selected for processing (69)
  • src/main/ipc/filesystem-list-files.test.ts
  • src/main/ipc/filesystem-list-files.ts
  • src/main/ipc/filesystem-markdown-document-listing.test.ts
  • src/main/ipc/filesystem/filesystem-read-handlers.ts
  • src/main/ipc/markdown-documents-ripgrep.test.ts
  • src/main/ipc/markdown-documents.ts
  • src/main/providers/filesystem-markdown-listing.ts
  • src/main/providers/filesystem-provider-contract.ts
  • src/main/providers/sftp-directory-test-fixture.ts
  • src/main/providers/ssh-directory-legacy-compatibility.test.ts
  • src/main/providers/ssh-directory-listing.ts
  • src/main/providers/ssh-directory-streaming-real.test.ts
  • src/main/providers/ssh-directory-streaming.test.ts
  • src/main/providers/ssh-file-listing.ts
  • src/main/providers/ssh-filesystem-download.test.ts
  • src/main/providers/ssh-filesystem-download.ts
  • src/main/providers/ssh-filesystem-provider-download-folder.test.ts
  • src/main/providers/ssh-filesystem-provider-sftp.ts
  • src/main/providers/ssh-filesystem-provider.test.ts
  • src/main/providers/ssh-filesystem-provider.ts
  • src/main/providers/ssh-listing-compatibility.test.ts
  • src/main/providers/ssh-markdown-document-listing.ts
  • src/main/providers/ssh-sftp-directory-close.ts
  • src/main/providers/ssh-sftp-filesystem-channel.ts
  • src/main/providers/ssh-sftp-filesystem-provider.test.ts
  • src/main/providers/ssh-sftp-filesystem-provider.ts
  • src/main/runtime/runtime-file-commands-constructor.ts
  • src/main/runtime/runtime-file-commands-search-runtime-files.ts
  • src/main/runtime/runtime-file-listing-producer-budgets.test.ts
  • src/main/runtime/runtime-file-target-execution-host.test.ts
  • src/main/ssh/ssh-directory-transfer-budget.ts
  • src/main/ssh/ssh-git-response-pending-retention.test.ts
  • src/main/ssh/ssh-git-response-stream-reader.ts
  • src/main/ssh/ssh-listing-response-retention.test.ts
  • src/main/ssh/ssh-response-payload.ts
  • src/main/ssh/ssh-response-pending-frames.test.ts
  • src/main/ssh/ssh-response-pending-frames.ts
  • src/main/ssh/system-ssh-file-transfer.ts
  • src/main/ssh/system-ssh-upload-plan-budget.test.ts
  • src/relay/dispatcher-rpc-routing.ts
  • src/relay/filesystem-capacity-error-wire.test.ts
  • src/relay/fs-directory-listing.ts
  • src/relay/fs-file-listing-paths.ts
  • src/relay/fs-handler-git-fallback.ts
  • src/relay/fs-handler-git-search-capacity.test.ts
  • src/relay/fs-handler-git-search-real-capacity.test.ts
  • src/relay/fs-handler-git-search.ts
  • src/relay/fs-handler-list-files-ignored.test.ts
  • src/relay/fs-handler-list-files.ts
  • src/relay/fs-handler.ts
  • src/relay/fs-markdown-document-listing.ts
  • src/relay/protocol.ts
  • src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts
  • src/renderer/src/components/editor/markdown-document-worktree-path-selector.ts
  • src/renderer/src/components/editor/monaco-markdown-doc-completions-retention.test.ts
  • src/renderer/src/components/editor/monaco-markdown-doc-completions.ts
  • src/renderer/src/components/editor/rich-markdown-image-insert.test.ts
  • src/renderer/src/components/editor/use-monaco-editor-decorations.ts
  • src/renderer/src/components/editor/useMarkdownDocuments.ts
  • src/renderer/src/runtime/runtime-legacy-inventory-budget.test.ts
  • src/renderer/src/runtime/runtime-legacy-inventory-budget.ts
  • src/renderer/src/runtime/runtime-legacy-quick-open-inventory-expiry.test.ts
  • src/renderer/src/runtime/runtime-legacy-quick-open-inventory.ts
  • src/shared/directory-listing-budget.ts
  • src/shared/file-inventory-budget.test.ts
  • src/shared/file-inventory-budget.ts
  • src/shared/git-grep-record-limit.ts
  • src/shared/markdown-document-paths.ts
  • src/shared/node-markdown-document-listing.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Comment on lines +102 to +106
let retainedBytes = budget.record([sourceDir, destinationDir], depth)
try {
for await (const entry of readDirectoryEntriesViaSftp(sftp, sourceDir, { signal })) {
const localName = sanitizeLocalDownloadFilename(entry.filename)
retainedBytes += budget.record([sourceDir, destinationDir, entry.filename, localName], depth)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Count only recorded entries when you release the budget.

budget.record increments entries once per call. Line 102 records the directory, and Line 106 records each entry. Line 144 releases usedLocalNames.size + 1 entries. The two counts are different in two cases:

  • Line 106 can succeed, and then Line 107 can throw for a duplicate local name. In that case the entry is recorded but not added to usedLocalNames. The release under-counts by one.
  • Line 106 adds the bytes for an entry. If Line 113 (classifySftpEntry) throws after Line 110, the release count is correct. The ordering is still fragile.

If the release under-counts, the next sibling transfer in the same run starts with a higher count. The first error ends the whole run, so the practical impact is small. Track a local counter that increments on each successful record call, and release that counter.

Proposed fix
-  let retainedBytes = budget.record([sourceDir, destinationDir], depth)
+  let retainedBytes = budget.record([sourceDir, destinationDir], depth)
+  let recordedEntries = 1
   try {
     for await (const entry of readDirectoryEntriesViaSftp(sftp, sourceDir, { signal })) {
       const localName = sanitizeLocalDownloadFilename(entry.filename)
       retainedBytes += budget.record([sourceDir, destinationDir, entry.filename, localName], depth)
+      recordedEntries += 1
...
-    budget.release(retainedBytes, usedLocalNames.size + 1)
+    budget.release(retainedBytes, recordedEntries)

if (signal?.aborted) {
return Promise.reject(fileListingCancellationError(signal))
}
const inventoryBudget = new FileInventoryBudget()

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply the inventory budget only to unbounded Git listings.

listFilesWithGit creates the budget even when maxResults is set. The ripgrep paths in fs-handler-list-files.ts and filesystem-list-files.ts create it only for unbounded listings. A bounded Git scan has early stops based only on directFileCandidates. Directory placeholders, gitlinks and excluded paths do not advance that count, but processPath still records them. In a large tree, a bounded Quick Open request can therefore throw FileInventoryCapacityError. It should return its first N results instead. The ignored-pass catch also rethrows this error, so the primary results are lost too.

Proposed fix
-  const inventoryBudget = new FileInventoryBudget()
+  const inventoryBudget = maxResults === undefined ? new FileInventoryBudget() : null
-          inventoryBudget.record(path)
+          inventoryBudget?.record(path)

...prev,
[worktreeId]: []
}))
toast.error(err instanceof Error ? err.message : 'Failed to list Markdown documents.')

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Repeated toasts on every refresh failure.

refreshMarkdownDocuments runs on every effect trigger (viewMode change, activeFile.id change) and after each save. If the listing keeps failing, the user sees a new error toast each time. Pass a stable id to toast.error so sonner deduplicates the toast.

🔧 Proposed fix
-          toast.error(err instanceof Error ? err.message : 'Failed to list Markdown documents.')
+          toast.error(err instanceof Error ? err.message : 'Failed to list Markdown documents.', {
+            id: 'markdown-document-list-error'
+          })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
toast.error(err instanceof Error ? err.message : 'Failed to list Markdown documents.')
toast.error(err instanceof Error ? err.message : 'Failed to list Markdown documents.', {
id: 'markdown-document-list-error'
})

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes

  • Aggregate memory budgets for file search — new shared FileInventoryBudget, DirectoryListingBudget, DirectoryTransferBudget, and git-grep-record-limit enforce retained-byte / entry / depth ceilings while data is produced. Capacity failures kill the producer, clear partial state, and surface typed error codes (-33009..-33011) across the relay envelope.
  • Markdown discovery split from generic listing — a dedicated fs.listMarkdownDocuments RPC and listMarkdownDocuments provider method enumerate Markdown under their own 20k-document / 8 MiB budget, preserving the existing ignore policy and folder-workspace alias paths. Older relays fall back to a 20 001-sentinel listFiles and fail rather than report a Markdown prefix as complete.
  • SSH response retention — SshResponsePayload and SshResponsePendingFrames replace the growable parts[] and frame-only backlog with byte-bounded, release-on-settle accumulation; sentinel metadata is validated before any chunks are retained, and old plain replies are byte-validated.
  • SFTP directory ownership — readDirectoryEntriesViaSftp streams directory packets, owns the remote handle, bounds CLOSE, and retires a channel that fails to close so the next read reopens a fresh one.
  • Renderer release paths — Monaco completion documents are keyed by model identity (WeakMap) so abandoned editors release their snapshots; useMarkdownDocuments keys its snapshot by scope and surfaces listing failures; the legacy Quick Open inventory is decoded against the byte budget and LRU-evicted by aggregate bytes.

I read the complete diff (all 69 files). Byte/chunk retention, frame-drop bookkeeping, the listSshFiles sentinel check, the folder-alias path mapping, and the SFTP close/abort boundaries all hold up against their tests — no actionable defects found.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@nwparker
nwparker force-pushed the nwparker/file-search-memory-bounds branch from 7910655 to 17066ec Compare October 5, 2026 04:18

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes

This run re-reviewed the 12 commits at 17066ec3be, focused on what changed since the prior pullfrog review (79106554d4) after the branch was rebased onto current main.

  • Removed the dead Markdown walker — deleted shared/node-markdown-document-discovery.ts and its test (the fs-walk enumerator superseded by the ripgrep collector), plus the now-unused maxVisitedEntries / maxDepth / visitedEntries / visitMarkdownDocumentListingEntry / retainMarkdownRelativePath from markdown-document-listing-limits.ts. No dangling references remain.
  • Fixed transfer-budget release accounting — downloadDirectoryTree in ssh-filesystem-download.ts now tracks retainedEntries (root plus one per recorded entry) instead of usedLocalNames.size + 1, which under-counted on the duplicate-local-name abort path and leaked DirectoryTransferBudget entries.
  • Hardened the SFTP download tests — withSftpDirectoryHandles fixture usage across the suite, a release-accounting assertion on the duplicate-name case, and it.each(['EOF', 'CLOSE']) final-abort cases proving no empty destination is created.
  • Cleanup — consolidated the MarkdownDocument import in filesystem-provider-contract.ts; reverted the incidental rich-markdown-image-insert.test.ts change (dropped the "Align image insertion test" commit).

The retainedEntries change is a genuine fix: on the duplicate-local-name throw the extra entry is already charged to the budget, so the old usedLocalNames.size + 1 released one fewer entry than it recorded. The added assertion fails against the old accounting.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@nwparker
nwparker requested a review from brennanb2025 as a code owner October 5, 2026 04:41

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes

This run re-reviewed the single commit at 972632804a since the prior pullfrog review (17066ec3be).

  • Localized the Markdown listing-failure fallback — useMarkdownDocuments now routes the non-Error toast text through translate('auto.components.editor.useMarkdownDocuments.listFailed', …) at settlement time, with the new en.json key; real Error messages still pass through unchanged, and pseudo-localized locales now get the fallback pseudo-localized too.
  • Added regression tests — a settlement-time localization test that fails against the previous hardcoded fallback, plus cases preserving real error detail and ignoring superseded/unmounted failures.

The navigation suite (17 tests) passes, and verify:localization-extraction, verify:localization-catalogs, and verify:localization-coverage all pass with the new key.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@nwparker
nwparker force-pushed the nwparker/file-search-memory-bounds branch from 9726328 to d4eeb8c Compare October 5, 2026 05:48

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes

This run re-reviewed the two commits at d4eeb8c931 since the prior pullfrog review (972632804a). Both are test-only changes to src/relay/fs-search-line-fragments.test.ts; no production code changed in this range.

  • Rewired the relay search fixture to real byte streams — createProcess() now returns a mocked child whose stdout is a real PassThrough, and run() writes Buffer chunks instead of emitting pre-decoded strings. This exercises both decoder boundaries production actually uses: searchWithRg calls stdout.setEncoding('utf-8') (string chunks), while searchWithGitGrep consumes raw Buffer chunks.
  • Fragmented UTF-8 coverage — the Unicode case writes one byte per chunk, so multi-byte sequences now cross chunk boundaries through the real decoder rather than arriving pre-assembled. The stale setEncoding spy assertion was correctly dropped since it no longer reflects the harness.
  • Dropped the ChildProcess cast — createProcess() no longer imports or casts to ChildProcess (stdout is Readable | null, which cannot be .write()-n); the return type is inferred.

I ran the suite (6 tests pass) and pnpm tc:node (clean). The prior reviews' concerns were already retired; no open Pullfrog threads remain on the PR.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@nwparker
nwparker force-pushed the nwparker/file-search-memory-bounds branch from d4eeb8c to 2f2058f Compare October 5, 2026 08:02
Credits @OrcaWin for the original bounded-record proposal.
Apply mobile limits before transport, list Markdown through its semantic producer, and retain complete directory results only within explicit capacity budgets. Stream SFTP directory packets and reject oversized transfer plans before reporting success; preserve narrow older-peer fallbacks and validate streamed response retention.
Keep one current document snapshot per consumer and attach completion candidates to the actual editor model lifetime. Resolve folder workspace roots through existing workspace identities so their previews and completions receive the same authorized listing as worktrees.
Leave unqualified inventories complete beyond 20,000 files. Charge retained paths and serialized bytes at local and relay producers, reject oversized full inventories explicitly, and bound old-peer response reassembly; keep caller-specific mobile and explicit result limits.
Send CLOSE even after cancellation, stop waiting after five seconds without an acknowledgement, and ignore late callbacks. Preserve the original capacity or cancellation error and close handles returned after an aborted OPENDIR.
@nwparker
nwparker force-pushed the nwparker/file-search-memory-bounds branch from 70112b5 to d06a8fb Compare October 6, 2026 04:06

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 33328970-42e3-4806-824e-c50cef367404
📥 Commits

Reviewing files that changed from the base of the PR and between 972632804accc257c9eea941e2de7dd63197ab71 and a4f286b.

📒 Files selected for processing (18)
  • src/main/ipc/filesystem-markdown-document-listing.test.ts
  • src/main/ipc/filesystem/filesystem-read-handlers.ts
  • src/main/ipc/markdown-documents-ripgrep.test.ts
  • src/main/ipc/markdown-documents.ts
  • src/main/providers/filesystem-markdown-listing.ts
  • src/main/providers/ssh-filesystem-provider.ts
  • src/main/providers/ssh-listing-compatibility.test.ts
  • src/main/providers/ssh-markdown-document-listing.ts
  • src/main/ripgrep/bundled-ripgrep-path.test.ts
  • src/main/ripgrep/bundled-ripgrep-stop.test.ts
  • src/main/ripgrep/bundled-ripgrep-stop.ts
  • src/relay/fs-handler.ts
  • src/relay/fs-markdown-document-launch.test.ts
  • src/relay/fs-markdown-document-listing.test.ts
  • src/relay/fs-markdown-document-listing.ts
  • src/relay/fs-search-line-fragments.test.ts
  • src/renderer/src/i18n/locales/en.json
  • src/shared/node-markdown-document-listing.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/i18n/locales/en.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Comment on lines +115 to +120
return rootPath === args.rootPath
? documents
: markdownDocumentsFromRelativePaths(
args.rootPath,
documents.map((document) => document.relativePath)
)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Rebuild aliased-root document paths with joinSearchRoot.

collectMarkdownDocuments sets each filePath with joinSearchRoot(rootPath, relativePath). This keeps native Windows separators. On the alias branch, this handler rebuilds each document with markdownDocumentsFromRelativePaths(args.rootPath, ...). That helper builds filePath as ${normalizedRoot}/${relativePath}.

Assume a Windows request root differs from the resolved root. Possible causes are a junction or symlink alias, different casing, or a trailing separator. The handler then returns mixed paths such as C:\repo/docs/README.md. That path does not match the native path the collector returns for the same file on the non-alias branch. Renderer code that compares paths can then treat one file as two documents. One example is Markdown link navigation that opens a duplicate tab.

The relay fallback in src/relay/fs-handler.ts (Lines 141-144) already corrects this case. It remaps filePath through joinSearchRoot after the same helper call. Use the same remap here.

🐛 Proposed fix
       return rootPath === args.rootPath
         ? documents
-        : markdownDocumentsFromRelativePaths(
-            args.rootPath,
-            documents.map((document) => document.relativePath)
-          )
+        : markdownDocumentsFromRelativePaths(
+            args.rootPath,
+            documents.map((document) => document.relativePath)
+          ).map((document) => ({
+            ...document,
+            filePath: joinSearchRoot(args.rootPath, document.relativePath)
+          }))

Import joinSearchRoot from ../../../shared/text-search-paths.

@nwparker
nwparker merged commit f59194d into main Oct 6, 2026
56 checks passed
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.

Bound newline-free git-grep fallback records

1 participant