Skip to content

fix(core): make Windows file-search fallback resilient - #146

Merged
quickbeard merged 1 commit into
mainfrom
windows-search-fallback
Jul 31, 2026
Merged

quickbeard merged 1 commit into
mainfrom
windows-search-fallback

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Summary

On Windows, @ file mentions frequently showed "No matching items" for every query. Windows is the only platform running the ripgrep-based search fallback (fff is disabled there by default, inherited from upstream e4300e9b74), and that fallback had three compounding failure modes:

  • The background index walk died silently (Effect.orDie in a forked fiber) if ripgrep failed, leaving fs.find returning [] forever with no user-visible signal. DIVERGENCES.md already documented this exact failure on Windows CI runners ("Windows's first-use rg.exe download left it permanently empty"), worked around there with a choco preinstall.
  • Effect.cached on ripgrep binary resolution memoizes failure exits with infinite TTL, so one failed download (offline, blocked proxy) bricked search for the process lifetime.
  • Directory entries were suffixed with path.sep, producing mixed-separator paths like src/component\ on Windows — breaking endsWith("/") directory-mime detection in codev run and poisoning tab drill-down queries with backslashes that can never match the /-normalized index.

Changes

  • packages/core/src/filesystem/search.ts — the index walk logs failures ("file index scan failed, file search may be empty") and retries forever on a 1s exponential backoff capped at 30s spacing, resetting accumulated state per attempt (a mid-session ripgrep install self-heals without duplicate entries). Directory entries use "/" instead of path.sep in both the ripgrep and fff layers. Fallback find queries normalize \ to / and collapse doubled slashes.
  • packages/core/src/ripgrep/binary.ts — success-only cache behind a semaphore instead of Effect.cached; failed downloads retry on next use.
  • packages/core/src/filesystem.ts, packages/core/src/tool/read-filesystem.ts — same "/" suffix fix for fs.list and the Read tool listing; fs.list also /-normalizes path.relative output.
  • Tests — directory expectations pinned to "src/" on all platforms (they previously encoded the path.sep bug, so the Windows CI job now guards this); new coverage for query normalization and for scan retry discarding a failed partial walk.
  • Docs — new DIVERGENCES.md row (all six upstream files, with on-conflict invariants) and a CHANGELOG.md entry under Unreleased.

Test plan

  • bun typecheck in packages/core and packages/opencode
  • bun test test/filesystem/ test/ripgrep.test.ts test/tool-read-filesystem.test.ts test/tool-read.test.ts test/location-filesystem.test.ts in packages/core
  • bun test test/server/httpapi-file.test.ts test/filesystem/filesystem.test.ts test/cli/run/footer.view.test.tsx in packages/opencode
  • Manual check on a Windows machine without rg.exe: log now names the scan failure; installing ripgrep mid-session populates @ without a restart

🤖 Generated with Claude Code

On Windows fff is disabled by default, so the @ mention popup and file
search depend entirely on the ripgrep fallback. A missing rg.exe plus one
failed download left the index permanently empty with no signal: the
background walk died silently (Effect.orDie in a forked fiber) and
Effect.cached memoized the failed binary resolution for the process
lifetime.

- Retry the index walk forever (1s exponential backoff capped at 30s),
  log each failure, and reset partial state per attempt so a mid-session
  ripgrep install self-heals without duplicating entries.
- Cache only successful ripgrep binary resolution; retry failed
  downloads on next use.
- Suffix and detect directory entries with "/" instead of path.sep in
  search, fs.list, and the Read tool listing, and "/"-normalize
  path.relative output, so entry paths are "/"-normalized on every
  platform (fixes endsWith("/") directory-mime detection and tab
  drill-down on Windows).
- Normalize "\" and doubled slashes in fallback find queries; they can
  never match the "/"-normalized index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quickbeard
quickbeard merged commit b100e7e into main Jul 31, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the windows-search-fallback branch July 31, 2026 14:56
quickbeard added a commit that referenced this pull request Aug 1, 2026
* feat(core): fall back to the CoDev mirror for ripgrep downloads

When rg is neither on PATH nor cached, CoDev Code downloads it from
GitHub releases on first use — which corporate networks commonly block,
leaving file search empty until the user intervenes (PR #146 made that
failure visible and retryable; codevhub#230 stages the binary at install
time, but only for users who ran codevhub).

The download tail now tries GitHub first and falls back to the landing
page's mirror of the official executables (codev-landing-page#64), which
hosts them raw — no archive, so the mirror path skips extraction and
stages the binary through a scoped temp dir before copying into place
(the cache check is bare existence, so a partial target must never
appear). Each attempt is bounded to 30s so a blackholing proxy can't
stall first search for minutes. The fallback engages via catchCause, not
catch: a proxy block page returns 200 + HTML, which passes
filterStatusOk and dies in extract as a defect, and that case must reach
the mirror too.

New mocked-HTTP tests cover the fallback order, success caching, and
failure non-caching; test/preload.ts now redirects XDG_CACHE_HOME to a
temp dir so tests exercising the real binary-resolution path can never
touch the developer's ~/.cache/codev.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(core): base64-encode the ripgrep mirror host

Same convention as codev-gateway's const.ts: keeps the host out of
casual greps, not a secret. The test's plain-text URL assertion now
doubles as proof the encoded value decodes to the right mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant