From c6f4fa0d1fb1def613a61ccef590fa7e9b328c41 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 23 Aug 2026 21:00:27 -0400 Subject: [PATCH] fix(core): never build fff index for workspace locations (#44563) --- packages/core/src/filesystem/search.ts | 5 +- packages/core/test/filesystem/search.test.ts | 73 +++++++++++++++----- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index 590bf91b9f3..91162fb1722 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -184,9 +184,12 @@ export const fffLayer = Layer.effect( export const layer = (options?: Options) => Layer.unwrap( Effect.gen(function* () { + const location = yield* Location.Service + // Workspace-backed Locations resolve in a remote sandbox; fff would index the local server directory + // in-process and serve wrong results. Ripgrep routes through the Location environment spawner. + if (location.workspaceID) return ripgrepLayer if (options?.fff === false || (options?.fff === undefined && process.platform === "win32") || !Fff.available()) return ripgrepLayer - const location = yield* Location.Service // Non-VCS locations can contain many repositories, so avoid eagerly content-indexing the entire aggregate tree. return location.vcs && !Protected.isHome(location.directory) ? fffLayer : ripgrepLayer }), diff --git a/packages/core/test/filesystem/search.test.ts b/packages/core/test/filesystem/search.test.ts index 95753707228..19c34b9b573 100644 --- a/packages/core/test/filesystem/search.test.ts +++ b/packages/core/test/filesystem/search.test.ts @@ -12,8 +12,25 @@ import { FileSystemSearch } from "@opencode-ai/core/filesystem/search" import { Location } from "@opencode-ai/core/location" import { Ripgrep } from "@opencode-ai/core/ripgrep" import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema" +import { Workspace } from "@opencode-ai/core/workspace" import { location } from "../fixture/location" +const ripgrepStub = (entry: string, onFind: (input: Ripgrep.FindInput) => void) => + Layer.succeed( + Ripgrep.Service, + Ripgrep.Service.of({ + find: (input) => + Effect.gen(function* () { + onFind(input) + if (input.onEntry) + yield* input.onEntry(FileSystem.Entry.make({ path: RelativePath.make(entry), type: "file" })) + return [] + }), + glob: () => Effect.succeed([]), + grep: () => Effect.succeed([]), + }), + ) + describe("FileSystemSearch", () => { test("honors wildcard directory rules from .gitignore", async () => { const directory = await mkdtemp(path.join(os.tmpdir(), "opencode-fff-ignore-")) @@ -50,6 +67,44 @@ describe("FileSystemSearch", () => { } }) + test("selects the ripgrep layer for workspace-backed locations even when vcs would pick fff", async () => { + const directory = await mkdtemp(path.join(os.tmpdir(), "opencode-search-workspace-")) + try { + // A local file that only an fff index of the server directory could surface. + // The fff-vs-ripgrep discrimination only bites where Fff.available() is + // true; elsewhere the layer choice already falls back to ripgrep. + await Bun.write(path.join(directory, "server-local.ts"), "server local") + let observed: Ripgrep.FindInput | undefined + const ref = Location.Ref.make({ + directory: AbsolutePath.make(directory), + workspaceID: Workspace.ID.make("wrk_test"), + }) + const layer = AppNodeBuilder.build(FileSystemSearch.node, [ + [ + Location.node, + Layer.succeed( + Location.Service, + Location.Service.of( + location(ref, { vcs: { type: "git", store: AbsolutePath.make(path.join(directory, ".git")) } }), + ), + ), + ], + [Ripgrep.node, ripgrepStub("remote.ts", (input) => (observed = input))], + ]) + + await Effect.runPromise( + Effect.gen(function* () { + const search = yield* FileSystemSearch.Service + const entries = yield* search.find({ query: "ts", type: "file" }) + expect(observed?.cwd).toBe(directory) + expect(entries.map((entry) => entry.path)).toEqual([RelativePath.make("remote.ts")]) + }).pipe(Effect.provide(layer), Effect.scoped), + ) + } finally { + await rm(directory, { recursive: true, force: true }) + } + }) + test("bounds a home scan even when home is detected as a repository", async () => { let observed: Ripgrep.FindInput | undefined const home = AbsolutePath.make(os.homedir()) @@ -63,23 +118,7 @@ describe("FileSystemSearch", () => { ), ), ], - [ - Ripgrep.node, - Layer.succeed( - Ripgrep.Service, - Ripgrep.Service.of({ - find: (input) => - Effect.gen(function* () { - observed = input - if (input.onEntry) - yield* input.onEntry(FileSystem.Entry.make({ path: RelativePath.make("src/index.ts"), type: "file" })) - return [] - }), - glob: () => Effect.succeed([]), - grep: () => Effect.succeed([]), - }), - ), - ], + [Ripgrep.node, ripgrepStub("src/index.ts", (input) => (observed = input))], ]) await Effect.runPromise(