Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion packages/pi-fff/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ function buildAtCompletionValue(path: string): string {
return path.includes(" ") ? `@"${path}"` : `@${path}`;
}

// heed refuses to reopen an LMDB env already open in the process, surfaced
// through FileFinder.create() as this error string. Matched to fall back to a
// transient (no-DB) finder for in-process subagents — see #760.
function isEnvAlreadyOpenError(error: string): boolean {
return /environment already open/i.test(error);
}

function createFffMentionProvider(
getItems: (query: string, signal: AbortSignal) => Promise<AutocompleteItem[]>,
): AutocompleteProvider {
Expand Down Expand Up @@ -396,7 +403,7 @@ export default function fffExtension(pi: ExtensionAPI) {
}

const { FileFinder } = await loadSdk();
const result = FileFinder.create({
let result = FileFinder.create({
basePath: cwd,
frecencyDbPath,
historyDbPath,
Expand All @@ -405,6 +412,21 @@ export default function fffExtension(pi: ExtensionAPI) {
enableFsRootScanning,
});

// In-process subagents (createAgentSession) load this extension a second
// time in the same process. LMDB opens an env at most once per path per
// process, so the shared frecency/history DBs are already owned by the
// parent session's finder — reopening them fails. Fall back to a
// transient finder without persistent scoring, exactly like aux finders
// do (#700). See #760.
if (!result.ok && isEnvAlreadyOpenError(result.error)) {
result = FileFinder.create({
basePath: cwd,
aiMode: true,
enableHomeDirScanning,
enableFsRootScanning,
});
}

if (!result.ok)
throw new Error(`Failed to create FFF file finder: ${result.error}`);

Expand Down
44 changes: 44 additions & 0 deletions packages/pi-fff/test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ function createMockFinder(): MockFinder {
};
}

let createImpl: ((options: unknown) => unknown) | undefined;

const finderModule = {
FileFinder: {
create: mock((options: unknown) => {
createCalls.push(options);
if (createImpl) {
const override = createImpl(options);
if (override !== undefined) return override;
}
const finder = createMockFinder();
finders.push(finder);
return { ok: true, value: finder };
Expand Down Expand Up @@ -163,6 +169,7 @@ beforeEach(() => {
createCalls.length = 0;
finders = [];
mixedSearchImpl = undefined;
createImpl = undefined;
scanProgressImpl = undefined;
delete process.env.PI_FFF_MODE;
delete process.env.FFF_ENABLE_HOME_SCAN;
Expand Down Expand Up @@ -256,6 +263,43 @@ describe("pi-fff autocomplete registration", () => {
expect(opts.enableHomeDirScanning).toBe(false);
});

// In-process subagents (createAgentSession) reopen the shared LMDB env,
// which fails. The main finder must fall back to a transient no-DB finder
// instead of throwing during session_start — see #760.
test("falls back to a transient finder when the LMDB env is already open", async () => {
process.env.FFF_FRECENCY_DB = "/tmp/fff-test/frecency";
process.env.FFF_HISTORY_DB = "/tmp/fff-test/history";
let firstCall = true;
createImpl = () => {
if (firstCall) {
firstCall = false;
return {
ok: false,
error:
"Failed to init frecency db: Failed to open frecency database env: environment already open in this program; close it to be able to open it again with different options",
};
}
return undefined;
};

const { ctx } = await start();

expect(ctx.ui.notify).not.toHaveBeenCalledWith(
expect.stringContaining("FFF init failed"),
"error",
);
expect(createCalls).toHaveLength(2);
const retry = createCalls[1] as {
frecencyDbPath?: string;
historyDbPath?: string;
};
expect(retry.frecencyDbPath).toBeUndefined();
expect(retry.historyDbPath).toBeUndefined();

delete process.env.FFF_FRECENCY_DB;
delete process.env.FFF_HISTORY_DB;
});

test("session_start survives hosts without addAutocompleteProvider", async () => {
const setup = createPi();
const ctx = {
Expand Down
Loading