From 4739f8aa6715ffc22e3080aa8444516eced8f09e Mon Sep 17 00:00:00 2001 From: ischindl Date: Fri, 7 Aug 2026 09:25:39 +0200 Subject: [PATCH] fix: eagerly warm extension host stores from engine TaskStores (#3340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Warm the extension-host task stores **up front** at dashboard startup instead of letting the first `fn_task_*` call lazily boot a second PostgreSQL pool per project. ## What changed `packages/cli/src/commands/dashboard.ts`: - After the dashboard boots, iterate every registered project (from `centralCoreForEngine.listProjects()`) and call `setHostTaskStore(p.path, engine.getTaskStore())` for each non-cwd project that already has a running `ProjectEngine`. - Reuses each engine's **existing** `TaskStore` directly — no new backend connection, no schema advisory-lock contention, no extra connection-pool exhaustion. - `cwd` is skipped because its store is already injected at startup. - Per-project failures are non-fatal (warn) and a failed project listing logs a single warn — dashboard startup never blocks on this. - `.changeset/extension-host-store-warmup.md` (patch, fix). ## Why Left on its own, the first extension tool call (`fn_task_update`, `fn_task_archive`, `fn_agent_show`, …) for a non-cwd project falls through to `createTaskStoreForBackend`, which boots a **second** PostgreSQL connection pool on demand. On busy hosts that lazy boot can time out, or the call stalls behind pool/startup contention — the classic "first `fn_task_*` call is slow or errors" experience. Pre-populating from the already-running engines removes that lazy worst-case path entirely. ## Verification - `pnpm verify:fast` — PASS (13 steps, 115s): CLI `tsup` build green, scoped typecheck/build green, boot smoke green (`fn --help` + real `serve` with `GET /api/health` 200). - Cherry-picked cleanly onto current `origin/main` (`5532019fd`); branch is up-to-date with `origin/main` at PR time. ## Files - `packages/cli/src/commands/dashboard.ts` (+30) - `.changeset/extension-host-store-warmup.md` (new) ## Summary by CodeRabbit * **Bug Fixes** * Improved dashboard startup reliability by reusing existing project task connections. * Prevented extension task tools from creating duplicate connection pools. * Added non-blocking warnings when individual project initialization or discovery fails. * Dashboard startup now reports how many project task stores were successfully prepared. Co-authored-by: gsxdsm --- .changeset/extension-host-store-warmup.md | 7 ++++++ packages/cli/src/commands/dashboard.ts | 30 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .changeset/extension-host-store-warmup.md diff --git a/.changeset/extension-host-store-warmup.md b/.changeset/extension-host-store-warmup.md new file mode 100644 index 0000000000..f798896cff --- /dev/null +++ b/.changeset/extension-host-store-warmup.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Warm extension-host task stores up front so fn_task_* tools never hit a lazy second-pool boot. +category: fix +dev: Populates setHostTaskStore for every registered project from the already-running ProjectEngine TaskStores at dashboard startup, so extension API tools (fn_task_update, fn_task_archive, fn_agent_show) find a cached store and never fall through to createTaskStoreForBackend, which could time out creating a second connection pool. cwd is skipped because its store is already injected. Non-fatal warnings on any per-project failure. \ No newline at end of file diff --git a/packages/cli/src/commands/dashboard.ts b/packages/cli/src/commands/dashboard.ts index 1407674539..556f05940b 100644 --- a/packages/cli/src/commands/dashboard.ts +++ b/packages/cli/src/commands/dashboard.ts @@ -2272,6 +2272,36 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?: } } + // FNXC:ExtensionHostStoreWarmup 2026-07-18-19:20: + // Pre-populate setHostTaskStore for all registered projects from already- + // running ProjectEngine TaskStores, so extension API tools (fn_task_archive, + // fn_task_update, etc.) find a cached store and never fall through to + // createTaskStoreForBackend (which times out creating a second pool). + // Reuses each engine's existing TaskStore directly — no new PG boot needed. + void (async () => { + try { + const projects = await centralCoreForEngine.listProjects(); + // Skip cwd — its store is already injected at line 928. + const nonCwd = projects.filter((p) => p.path !== cwd); + for (const p of nonCwd) { + try { + const engine = engineManager.getEngine(p.id); + if (!engine) continue; + setHostTaskStore(p.path, engine.getTaskStore()); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + logSink.warn(`Failed to warm extension store for ${p.name} (${p.path}): ${msg}`, "extension"); + } + } + if (nonCwd.length > 0) { + logSink.log(`Warmed extension host stores for ${nonCwd.length} project(s)`, "extension"); + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + logSink.warn(`Failed to list projects for store warmup: ${msg}`, "extension"); + } + })(); + disposeCallbacks.push(async () => { if (hybridExecutor) { await hybridExecutor.shutdown();