Repository navigation
fix(explorer): stop the file filter claiming it searched everything - #6
Merged
Merged
Conversation
The Explore pane's filename filter and Quick Open searched local files by scanning
the whole workspace and stopping at 20 001 paths in ripgrep's traversal order.
Whether a file was inside that prefix was arbitrary, and the Explore pane never
showed the truncation flag, so a filter could answer a file that exists with "No
files match this filter" — the reported bug, on a repository large enough that a
deep file fell outside the prefix. A local filter was also broken outright: its
projection was gated on a query-scoped listing, and a local listing is unscoped,
so every local query resolved to zero rows.
Both panes now ask the execution host for the files that match the query, which is
where SSH and remote runtimes already did it:
- `searchQuickOpenFilePaths` takes a matcher mode: the existing fuzzy ranker for
Quick Open, and a new `NameFilterPathMatcher` for the Explore filter. The matcher
keeps the pane's substring-AND semantics, counts every match, and retains the
lexicographically first page, so the tree stays a stable sorted prefix.
- `fs:searchFilePaths` exposes that to the window as
`{ files, totalCount, truncated }`, sharing the `fs:listFiles` cancellation
registry so a superseded keystroke kills the previous scan.
- A local workspace takes that path once the query is non-empty; an empty query
keeps the unscoped listing, so Quick Open still shows files before you type.
SSH and remote runtimes are unchanged, and now also carry the `totalCount` their
replies already contained.
- The Explore pane reports a bounded page honestly: "Showing the first N of M
matches" when the host counted matches, "Only part of this workspace was
searched" when it could not. "No files match this filter" is now reachable only
for a scan that completed.
`QUICK_OPEN_LISTING_MAX_RESULTS` is untouched. It is an OOM bound from stablyai#10179,
aliased by the readdir fallback and clamped by the relay, so raising it would
re-open that hole instead of fixing the filter.
Design, contract, and staging live in docs/reference/file-path-filtering-at-scale.md,
linked from AGENTS.md.
`pnpm tc` is clean, 9 780 tests pass across right-sidebar/runtime/web/main-ipc/relay
(`filesystem-search-file-paths-real.test.ts` drives real ripgrep over 20 003 files),
and `check:code-quality:changed` reports no findings.
nplez1
pushed a commit
that referenced
this pull request
Oct 21, 2026
…tablyai#31066) * fix(cli): worktree create sends the host --project/--host selected Codex review 6 #6: the CLI picked the right setup but sent only its repo id, so a repo id set up on two hosts failed as selector_ambiguous; and --project-host-setup ignored --host when setup ids repeated. worktree.create gains optional repoHostId (older hosts drop it and keep the old behaviour); the runtime narrows the repo selector to that host. * refactor(runtime): the repo selector carries its host through every lookup Review follow-up (Codex + Opus on stablyai#31066): a separate repoHostId was dropped by the startup-agent lookup, the CLI's GitLab link check and the create dedupe key. RuntimeRepoSelector now carries the host with the selector, so showRepo/resolveRepoSelector and startup-agent resolution keep it; the create dedupe key includes it; repo.show gains the same optional repoHostId; the CLI spreads one CliRepoTarget into every repo lookup. Handler-level tests cover worktree.create (plain, startupAgent, startupDraft), agent.launch, repo.show and the dedupe key. * test(runtime): drop a needless cast in the agent.launch host test; fold an import
nplez1
pushed a commit
that referenced
this pull request
Oct 21, 2026
…p ones (stablyai#31096) * feat(ci): ratchet raw repo connectionId host reads; one drop-in resolver Codex review 6 class fix (#4/#6/#7): host decisions read off a repo row's legacy connectionId miss rows that name SSH only as executionHostId. - getRepoRoutingConnectionId (shared/execution-host.ts): drop-in for a raw repo.connectionId routing check that also finds executionHostId-only SSH. - check-repo-host-read-ratchet: per-file count of raw repo connectionId reads in src/main, src/shared, src/cli; counts may only shrink. - Migrated 66 cheap reads: runtime GitHub/GitLab commands, PR/MR head ref fetches, preserved-branch cleanup, ref queries, base-ref IPC, worktree preparation, trash sweep, scan cache TTL, usage metadata. * fix(ipc): PR base IPC routes executionHostId-only SSH rows; ratchet is a stated lower bound Review follow-up (Opus on stablyai#31096): - worktrees:resolvePrBase decided git/gh routing on the raw connectionId while its head-ref fetches already used the resolver; one resolved connectionId now drives all of it (test fails on main). - getRepoRoutingConnectionId's contract is stated exactly: never local when the raw field is set; on a contradictory ssh row executionHostId wins. - The ratchet regex no longer matches report.connectionId; header and docs say it is a lower bound. - Regression tests for a runtime GitHub and GitLab command site. * test(ci): pin the repo host-read ratchet step to the static phase * fix(ci): repo host-read ratchet skips verbatim field copies into projected rows A copy of connectionId into a projected row decides nothing; migrating it to the resolver would change what the projection publishes. The sidebar feed catalog's copy (added after the baseline) and two older projections no longer count; baseline pruned to 91.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
The file filter in the file browser was reading only the first page of a very long list of files, and it never said so — so a file further down that list came back as "no files match this filter". On a local workspace it was worse than that: the filter threw away its own results and returned nothing for any search.
Now the filter asks the computer for the files that match what you typed, wherever they are in the workspace. When there are more matches than it will show, it says how many it found and how many it is showing, and asks you to type a bit more.
What Changed
Before, on a local workspace:
droveranddrover.eve.After:
files.length >= 20 001.Mechanism
QuickOpenPathRankergains a sibling,NameFilterPathMatcher, insrc/shared/quick-open-path-search.ts. The Explore filter keeps its documented substring-AND semantics (it is deliberately not pointed at the fuzzy ranker), counts every match, and retains the lexicographically first page so the tree stays a stable sorted prefix rather than an arbitrary subset.searchQuickOpenFilePathstakes amode(quick-open|name-filter) over one ripgrep pass; the shared tokenizer/predicate is now used by both the host matcher and the renderer projection, so the two cannot drift.fs:searchFilePathsIPC returns{ files, totalCount, truncated }and shares thefs:listFilescancellation registry, so a superseded keystroke kills the previous scan.searchRuntimeFilePathsuses it for a local workspace; an empty query keeps the unscoped listing, so Quick Open still lists files before you type.totalCounttheir replies already carried; their wire format is unchanged.QUICK_OPEN_LISTING_MAX_RESULTSis untouched.Why
The reported symptom had two independent causes, and the tempting fix — remove the 20 001 limit — is the wrong one:
resolvedQuery === query). A local listing is unscoped (resolvedQuery === undefined), so every local query resolved to[]. This alone explains zero results for any query.QUICK_OPEN_READDIR_MAX_FILESaliases it and the readdir fallback throws past it, the relay clamps to it viaresolveQuickOpenResultLimit, and the runtime command path imports it. Raising it would also re-introduce the cost it exists to prevent — the renderer fuzzy-ranks the whole retained array per keystroke.So the bound stays, and a filtered query stops using the bounded listing at all: it gets its own query-scoped search with a smaller page. That is the design the remote paths already used, now shared with local.
Alternatives considered: raising the cap (rejected above); client-side paging with cursors (rejected — paging over a mutating filesystem needs snapshot tokens and host-side caches, which is the memory the bound exists to bound, and typing one more character is better UX than a "load more" button); streaming results over a new RPC opcode (deferred —
docs/reference/remote-wire-compatibility.mdRule 2 says a new stream opcode must be capability-negotiated, which is the follow-up stage).Design, contract, staging, and the test strategy are written up in
docs/reference/file-path-filtering-at-scale.md(linked from AGENTS.md, and allow-listed in.gitignorethe way that file's own convention requires for a tracked reference doc).Linked Issue
No issue — reported directly, fixed on
fix/explore-file-filter-partial-results, which is cut fromorigin/mainso its delta is this one commit.Visual Proof
Pending — no before/after captured. This change is visible in the Explore pane (a partial-result notice replaces a false "No files match"), but this worktree has no rendered-app harness, so it is verified at the unit and integration level instead: see Testing. Both states are easy to capture for anyone with a workspace over ~20 000 files.
Testing
pnpm tc— clean (node/cli/web projects).9 780 tests pass across
src/renderer/src/components/right-sidebar,src/renderer/src/runtime,src/renderer/src/web,src/main/ipc,src/relay(1 073 files, 18 skipped), run on theorigin/mainbase.src/main/ipc/filesystem-search-file-paths-real.test.tsis new and drives real ripgrep over a temp workspace: 20 002 filler files plussrc/a/b/drover.eve_schema, asserting the query-scoped name-filter search returns that file withtotalCount: 1— the reported scenario. Plus a broad-query case (bounded sorted page + exact count +truncated) and a fuzzy-mode regression case. The suite skips itself when norgbinary is present.The gate that caused the zero-rows bug has a regression test that fails on the previous code:
use-file-explorer-name-filter.test.ts→ "filters a local full listing that no query produced".check:code-quality:changed,check:max-lines-ratchet,verify:localization-catalog,verify:localization-extraction,check:readme-local-links— all pass.Platforms: macOS only. The SSH/relay and runtime paths are deliberately unchanged, and the added code is path-agnostic (no new shell, shortcut, or path handling). Windows/Linux and a real SSH host were not run locally — CI covers the unit matrix.
Not run locally:
pnpm lintin full,pnpm build.I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Implemented with an agent (DeepSeek v4.1 Flash in the
piharness), with a design consultation from Kimi K3 (Fireworks) and a review pass from a stronger advisor model. The maintainer reported the bug and chose the approach among the presented options.Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
resolveAuthorizedPathasfs:listFiles, honours the sameexcludePaths(nested worktrees) and the same hidden-dir/node_modulesblocklist, and reuses the sender-scoped cancellation registry. No new file access is granted.wslAwareSpawnand the existing path normalization are untouched; the new matcher compares lowercased/-separated relative paths, as the renderer already did.truncatedwithout a count, which the pane now reports honestly). Paired-web gained afiles.searchPathsimplementation for the new API method. An exact name-filter total over RPC, with version negotiation and a legacy fallback, is stage 2 in the design doc.fs:searchFilePathsis a new channel (same-version local IPC). Remote wire format unchanged;totalCountwas already in the runtime reply.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)