dmtrKovalenko / dmtrKovalenko/fff
pi-fff blocks Pi /new and /resume while waiting for FileFinder scan on session_start
- Dominant language
- Rust
- Stars
- 10.7k
- Forks
- 446
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 39
Description
## Summary
`@ff-labs/pi-fff` currently blocks Pi session replacement (`/new` and `/resume`) because the `session_start` handler eagerly initializes `FileFinder` and awaits the initial scan.
In `packages/pi-fff/src/index.ts`:
```ts
pi.on("session_start", async (_event, ctx) => {
try {
activeCwd = ctx.cwd;
if (shouldEnableMentions()) applyEditorMode(ctx);
await ensureFinder(activeCwd);
} catch (e: unknown) {
ctx.ui.notify(...);
}
});
```
`ensureFinder()` calls:
```ts
const result = FileFinder.create({ basePath: cwd, ... });
...
await finder.waitForScan(15000);
```
Because Pi recreates/reloads the session runtime when switching sessions, this means `/new` and selecting a session from `/resume` can hang until `FileFinder.create()` / `waitForScan(15000)` finishes.
## Impact
- `/resume`: after selecting a session, Pi appears stuck before the resumed session is usable.
- `/new`: after pressing Enter, Pi appears stuck before the new session is usable.
- This is especially noticeable in larger workspaces where scanning/watching takes non-trivial time.
- Disabling `@ff-labs/pi-fff` makes `/new` and `/resume` fast again.
## Expected behavior
Pi session startup should not be blocked by a file index warmup. The finder can still be warmed in the background, and actual fff tool calls / @-mention autocomplete can await the same in-flight initialization if needed.
## Suggested fix
Make the `session_start` warmup non-blocking, e.g. schedule it after the session start event returns:
```ts
let lifecycleId = 0;
pi.on("session_start", async (_event, ctx) => {
try {
activeCwd = ctx.cwd;
const sessionLifecycleId = ++lifecycleId;
if (shouldEnableMentions()) applyEditorMode(ctx);
setTimeout(() => {
if (sessionLifecycleId !== lifecycleId) return;
ensureFinder(activeCwd).catch((e: unknown) => {
if (sessionLifecycleId !== lifecycleId) return;
ctx.ui.notify(
`FFF init failed: ${e instanceof Error ? e.message : String(e)}`,
"error",
);
});
}, 0);
} catch (e: unknown) {
ctx.ui.notify(
`FFF init failed: ${e instanceof Error ? e.message : String(e)}`,
"error",
);
}
});
pi.on("session_shutdown", async () => {
lifecycleId++;
destroyFinder();
});
```
I also found it safer for `ensureFinder()` to return the local `createdFinder` after `waitForScan()` rather than the mutable global `finder`, so a shutdown during warmup cannot accidentally return a destroyed/replaced finder.
## Local validation
I tested a local patch with:
```bash
cd packages/pi-fff
npx tsc --noEmit
```
and verified that importing the extension still returns a valid factory. With the patch, `/new` and `/resume` are no longer blocked by the scan warmup; the first actual fff operation may still await initialization, which seems like the expected tradeoff.
## Related issues I found
I did not find an exact duplicate for `pi-fff` blocking Pi `/new` or `/resume`. Some related `pi-fff`/watcher issues:
- #403: parallel `ffgrep` calls and `ensureFinder` singleton synchronization
- #439: `aiMode: true` watcher file descriptor usage on macOS
- #476: macOS segfault while watching directories during clone operations
This issue seems specifically about lifecycle behavior in the Pi extension: doing blocking index warmup inside `session_start`.
## Environment
- `@ff-labs/pi-fff`: 0.8.1
- Pi: 0.75.1
- Node.js: v24.13.1
- OS: macOS / Darwin arm64
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.