Skip to content

pi-fff blocks Pi /new and /resume while waiting for FileFinder scan on session_start #477

@cokekitten

Description

@cokekitten

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:

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:

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:

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:

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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions