feat: faster dashboard and serve startup#2132
Conversation
|
Warning Review limit reached
Next review available in: 26 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughDashboard and serve startup now share phase timing, PostgreSQL task-store lifecycle handling, project-aware engine initialization, and deferred ProjectEngine work. Serve can listen while non-primary engines warm in the background. ChangesFaster startup flow
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Serve
participant TaskStore
participant ProjectEngineManager
participant ProjectEngine
participant HTTPServer
Serve->>TaskStore: boot shared backend task store
Serve->>ProjectEngineManager: start background engines and reconciliation
Serve->>ProjectEngineManager: ensure selected primary engine
ProjectEngineManager->>ProjectEngine: construct and start primary engine
Serve->>HTTPServer: listen and log time-to-listen
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR shortens dashboard and serve startup time. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (4): Last reviewed commit: "fix(ci): bundle plugin schema shim from ..." | Re-trigger Greptile |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/engine/src/project-engine.ts (1)
1102-1148: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winPartial-start
stop()cleanup never resetsshuttingDown, permanently disabling deferred work and merge enqueue for this instance.The class-level comment above
startupGeneration(lines 489-493) states the invariant: "stop() clears shuttingDown so the engine can restart." The full-teardown path honors that at line 1243 (this.shuttingDown = false;). This new!this.startedearly-return branch does not — it returns at line 1146 having only setthis.shuttingDown = trueat the top ofstop().Once this branch executes on an engine instance (e.g.
stop()called whilestart()failed/was still in-flight before reachingthis.started = true),shuttingDownis stucktrueforever on that object. Every future call on the same instance is affected:
deferredStartupAborted()(line 939) short-circuits onthis.shuttingDown, so a subsequentstart()on the same instance will never run notifiers, OAuth, automation sync, or startCronRunner.internalEnqueueMerge()(line 2499) returnsfalseimmediately, so auto-merge ontask:moved, manualonMerge, and the periodic merge-retry sweep silently stop working — with no error surfaced.This is currently untested:
project-engine-deferred-startup.test.ts's stop-related test only exercisesstop()afterstart()fully completed (the full-teardown path), not this partial-start branch.🐛 Proposed fix
if (!this.started) { try { this.gridlockDetector?.stop(); this.cronRunner?.stop(); this.oauthExpiryMonitor?.stop(); this.oauthRefreshScheduler?.stop(); this.oauthValidityLogger?.stop(); this.notificationService?.stop(); this.notifier?.stop(); this.setAutomationSubsystemHealth("not-initialized", "Automation subsystem stopped (partial start)"); } catch { // Best-effort partial cleanup } + this.shuttingDown = false; return; }Per the repo's bug-regression-test guideline, a fix here should also add a test that reproduces the actual symptom (start a fresh engine, force an early throw before
this.started = true, callstop(), thenstart()again and assert deferred work/merge enqueue actually runs) rather than only asserting the flag flips — a repro-only test on the flag itself would be incomplete.Based on learnings,
packages/engine/src/__tests__/project-engine-deferred-startup.test.ts:...: "Bug regression tests must assert the general invariant across all known surfaces and reproduce the original symptom through an automated test. Repro-only tests are incomplete."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/project-engine.ts` around lines 1102 - 1148, Reset shuttingDown before the !this.started early return in stop(), preserving the restart invariant also handled by the full-teardown path. Add a regression test in project-engine-deferred-startup.test.ts that forces start() to fail before started becomes true, calls stop(), then restarts the same engine and verifies deferred startup work and internalEnqueueMerge() execute successfully.Source: Coding guidelines
🧹 Nitpick comments (4)
scripts/build-workspace.mjs (2)
449-451: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove unnecessary nullish coalescing.
planWorkspaceBuildis guaranteed to returnskippedPackagesas an array (initialized on line 356). The defensive?? []fallback is unnecessary.♻️ Proposed fix
const skippedPluginNames = plan.skippedPlugins.map((pkg) => pkg.name); - const skippedPackageNames = (plan.skippedPackages ?? []).map((pkg) => pkg.name); + const skippedPackageNames = plan.skippedPackages.map((pkg) => pkg.name);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/build-workspace.mjs` around lines 449 - 451, Remove the unnecessary nullish-coalescing fallback from the skippedPackageNames assignment in the workspace build planning flow. Since planWorkspaceBuild guarantees plan.skippedPackages is an array, map directly over plan.skippedPackages while preserving the existing skippedPluginNames handling.
70-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueEnsure
cache.entriesis not null to match the JSDoc return type and legacy fallback logic.While optional chaining downstream prevents a crash,
typeof nullevaluates to"object". This allows the function to return{ version: 2, entries: null }if a malformed file is read, which violates theRecordreturn type declared in the JSDoc. The legacy check on line 77 already correctly guards against this with&& legacy.entries.♻️ Proposed fix
const cache = readJsonCache(pluginBuildCachePath(rootDir), null); - if (cache && cache.version === BUILD_CACHE_VERSION && typeof cache.entries === "object") { + if (cache && cache.version === BUILD_CACHE_VERSION && typeof cache.entries === "object" && cache.entries) { return cache; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/build-workspace.mjs` around lines 70 - 73, Update the cache validation in the build-cache loading logic to require cache.entries to be non-null in addition to being an object. Preserve the existing version check and fallback behavior so malformed entries do not satisfy the return path or violate the declared Record type.packages/cli/src/__tests__/startup-phase.test.ts (1)
5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer fake timers over a real
setTimeout.The success test awaits a real 5ms timer to simulate async work. As per coding guidelines, tests should "prefer narrow seams, in-memory fakes, shared harnesses, targeted assertions, and fake timers" rather than real waits.
♻️ Suggested fix using fake timers
- it("logs duration on success", async () => { - const log = vi.fn(); - const result = await phaseTime("demo", async () => { - await new Promise((r) => setTimeout(r, 5)); - return 42; - }, log, "test"); + it("logs duration on success", async () => { + vi.useFakeTimers(); + const log = vi.fn(); + const resultPromise = phaseTime("demo", async () => { + await new Promise((r) => setTimeout(r, 5)); + return 42; + }, log, "test"); + await vi.advanceTimersByTimeAsync(5); + const result = await resultPromise; + vi.useRealTimers();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/__tests__/startup-phase.test.ts` around lines 5 - 16, Update the “logs duration on success” test around phaseTime to use Vitest fake timers instead of a real setTimeout. Enable fake timers, advance the timer while the phase promise is pending, then await the result and preserve the existing result and log assertions; restore real timers afterward.Source: Coding guidelines
packages/cli/src/commands/serve.ts (1)
699-759: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winMirror dashboard.ts's single
getGlobalSettingsStore().getSettings()read here too.
dashboard.tsnow resolves Claude/Droid/Llama CLI paths from one sharedglobalSettingsread (seepackages/cli/src/commands/dashboard.tslines 1683-1715), butserve.tsstill performs three separate sequentialstore.getGlobalSettingsStore().getSettings()calls, each on the time-to-listen critical path (this all runs beforecreateServer/listen). Given the PR's stated goal of speeding up time-to-listen for bothdashboardandserve, this is a straightforward win that was applied to one surface but not the other.♻️ Suggested consolidation
- const claudeCliPaths = await (async () => { - try { - const globalSettings = await store.getGlobalSettingsStore().getSettings(); - const result = resolveClaudeCliExtensionPaths(globalSettings); - ... - const droidCliPaths = await (async () => { - try { - const globalSettings = await store.getGlobalSettingsStore().getSettings(); - const result = resolveDroidCliExtensionPaths(globalSettings); - ... - const llamaCppPaths = await (async () => { - try { - const globalSettings = await store.getGlobalSettingsStore().getSettings(); - const result = resolveLlamaCppExtensionPaths(globalSettings); - ... + let claudeCliPaths: string[] = []; + let droidCliPaths: string[] = []; + let llamaCppPaths: string[] = []; + try { + const globalSettings = await store.getGlobalSettingsStore().getSettings(); + const claude = resolveClaudeCliExtensionPaths(globalSettings); + setCachedClaudeCliResolution(claude.resolution); + claudeCliPaths = claude.paths; + const droid = resolveDroidCliExtensionPaths(globalSettings); + setCachedDroidCliResolution(droid.resolution); + droidCliPaths = droid.paths; + const llama = resolveLlamaCppExtensionPaths(globalSettings); + setCachedLlamaCppResolution(llama.resolution); + llamaCppPaths = llama.paths; + } catch (err) { + console.warn(`[extensions] Unable to evaluate CLI extension settings: ${err instanceof Error ? err.message : String(err)}`); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/commands/serve.ts` around lines 699 - 759, Consolidate the three CLI extension resolution blocks in the serve startup flow to perform one shared store.getGlobalSettingsStore().getSettings() read, then pass that result to resolveClaudeCliExtensionPaths, resolveDroidCliExtensionPaths, and resolveLlamaCppExtensionPaths. Preserve each resolver’s warning, cache-update, error fallback, and returned paths behavior while removing the repeated sequential settings reads.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/engine/src/project-engine.ts`:
- Around line 1102-1148: Reset shuttingDown before the !this.started early
return in stop(), preserving the restart invariant also handled by the
full-teardown path. Add a regression test in
project-engine-deferred-startup.test.ts that forces start() to fail before
started becomes true, calls stop(), then restarts the same engine and verifies
deferred startup work and internalEnqueueMerge() execute successfully.
---
Nitpick comments:
In `@packages/cli/src/__tests__/startup-phase.test.ts`:
- Around line 5-16: Update the “logs duration on success” test around phaseTime
to use Vitest fake timers instead of a real setTimeout. Enable fake timers,
advance the timer while the phase promise is pending, then await the result and
preserve the existing result and log assertions; restore real timers afterward.
In `@packages/cli/src/commands/serve.ts`:
- Around line 699-759: Consolidate the three CLI extension resolution blocks in
the serve startup flow to perform one shared
store.getGlobalSettingsStore().getSettings() read, then pass that result to
resolveClaudeCliExtensionPaths, resolveDroidCliExtensionPaths, and
resolveLlamaCppExtensionPaths. Preserve each resolver’s warning, cache-update,
error fallback, and returned paths behavior while removing the repeated
sequential settings reads.
In `@scripts/build-workspace.mjs`:
- Around line 449-451: Remove the unnecessary nullish-coalescing fallback from
the skippedPackageNames assignment in the workspace build planning flow. Since
planWorkspaceBuild guarantees plan.skippedPackages is an array, map directly
over plan.skippedPackages while preserving the existing skippedPluginNames
handling.
- Around line 70-73: Update the cache validation in the build-cache loading
logic to require cache.entries to be non-null in addition to being an object.
Preserve the existing version check and fallback behavior so malformed entries
do not satisfy the return path or violate the declared Record type.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c1a4e0bc-ef32-44f2-b627-2cafde1faaa1
📒 Files selected for processing (14)
.changeset/faster-startup-p0.mddocs/plans/2026-07-14-001-feat-faster-startup-plan.mdpackages/cli/src/__tests__/startup-phase.test.tspackages/cli/src/commands/__tests__/serve.test.tspackages/cli/src/commands/dashboard.tspackages/cli/src/commands/serve.tspackages/cli/src/startup-phase.tspackages/core/src/postgres/startup-factory.tspackages/engine/src/__tests__/project-engine-deferred-startup.test.tspackages/engine/src/__tests__/project-engine-manager.test.tspackages/engine/src/project-engine-manager.tspackages/engine/src/project-engine.tsscripts/__tests__/build-workspace.test.mjsscripts/build-workspace.mjs
Share the CLI-booted PostgreSQL TaskStore with the cwd engine on dashboard (serve parity), inject only when project roots match, defer non-route-critical ProjectEngine work (OAuth-ordered notifiers, automation syncs, merge sweep), and stop await-startAll before serve listen. Add phase timing across factory, dashboard, and serve.
Clear stale merging statuses on the ProjectEngine critical path and only defer auto-merge enqueue. Apply serve --paused before ensureEngine/startAll. Invalidate deferred startup work with a generation counter so stop() cannot resume OAuth after clearing shuttingDown. Update serve tests for ensure-on- primary boot.
Greptile: isolate deferred notifier failures, start CronRunner only after schedule sync, realpath-match externalTaskStore roots, clean up partial starts on stop. Build: content-hash skip for non-plugin workspace packages (v2 workspace-build-cache) so repeated pnpm build is incremental.
157a03c to
1d6753b
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plans/2026-07-14-001-feat-faster-startup-plan.md`:
- Around line 277-295: Add explicit stop-aware cancellation to the deferred
startup chain initiated by ProjectEngine.start: track a generation or abort
signal, validate it before each deferred phase, and prevent any phase from
running after stop(). Add coverage that stops the engine before the deferred
chain executes and verifies no deferred work resumes or accesses closed
services.
In `@packages/cli/src/commands/__tests__/serve.test.ts`:
- Around line 700-728: Update ensureEngine so each project receives
externalTaskStore only when its root matches the configured project root,
omitting the shared store for nonmatching projects to preserve isolation. Ensure
the starting entry is removed in a finally block regardless of whether
engine.start succeeds or rejects, allowing failed starts to retry. Add
regression assertions covering this invariant across all relevant project
surfaces.
In `@packages/engine/src/project-engine.ts`:
- Around line 679-700: The timeout path in awaitPriorMergeBodySettle must not
clear mergeBodyInFlight while the prior merge is still running; retain the latch
until the underlying promise actually settles, and apply the same correction to
the shutdown handling around the merge-body cleanup at the referenced later
logic. Ensure subsequent merges cannot start concurrently with an orphaned merge
body.
- Around line 500-506: Update stop() to retain and await any in-flight deferred
subsystem startup before tearing down the engine, while invalidating
startupGeneration so resumed startupMergeEnqueue() work aborts after stop or
restart. Ensure both full-stop and partial-start paths perform the complete
idempotent cleanup, including runtime, research, reconciler, and tunnel
resources, only after deferred startup settles.
- Around line 943-946: Replace the dynamic import in the project-engine
initialization flow with a static top-level import from `@fusion/core`, including
AutomationStore and the sync helpers used by this module. Update the surrounding
logic to use those imported symbols directly while preserving the existing
initialization behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f020dc62-3b3c-47b9-b679-d07d274a0b4e
📒 Files selected for processing (12)
.changeset/faster-startup-p0.mddocs/plans/2026-07-14-001-feat-faster-startup-plan.mdpackages/cli/src/__tests__/startup-phase.test.tspackages/cli/src/commands/__tests__/serve.test.tspackages/cli/src/commands/dashboard.tspackages/cli/src/commands/serve.tspackages/cli/src/startup-phase.tspackages/core/src/postgres/startup-factory.tspackages/engine/src/__tests__/project-engine-deferred-startup.test.tspackages/engine/src/__tests__/project-engine-manager.test.tspackages/engine/src/project-engine-manager.tspackages/engine/src/project-engine.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/cli/src/startup-phase.ts
- .changeset/faster-startup-p0.md
- packages/cli/src/tests/startup-phase.test.ts
- packages/engine/src/tests/project-engine-manager.test.ts
- packages/core/src/postgres/startup-factory.ts
- packages/cli/src/commands/serve.ts
- packages/engine/src/tests/project-engine-deferred-startup.test.ts
- packages/cli/src/commands/dashboard.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plans/2026-07-14-001-feat-faster-startup-plan.md`:
- Around line 277-295: Add explicit stop-aware cancellation to the deferred
startup chain initiated by ProjectEngine.start: track a generation or abort
signal, validate it before each deferred phase, and prevent any phase from
running after stop(). Add coverage that stops the engine before the deferred
chain executes and verifies no deferred work resumes or accesses closed
services.
In `@packages/cli/src/commands/__tests__/serve.test.ts`:
- Around line 700-728: Update ensureEngine so each project receives
externalTaskStore only when its root matches the configured project root,
omitting the shared store for nonmatching projects to preserve isolation. Ensure
the starting entry is removed in a finally block regardless of whether
engine.start succeeds or rejects, allowing failed starts to retry. Add
regression assertions covering this invariant across all relevant project
surfaces.
In `@packages/engine/src/project-engine.ts`:
- Around line 679-700: The timeout path in awaitPriorMergeBodySettle must not
clear mergeBodyInFlight while the prior merge is still running; retain the latch
until the underlying promise actually settles, and apply the same correction to
the shutdown handling around the merge-body cleanup at the referenced later
logic. Ensure subsequent merges cannot start concurrently with an orphaned merge
body.
- Around line 500-506: Update stop() to retain and await any in-flight deferred
subsystem startup before tearing down the engine, while invalidating
startupGeneration so resumed startupMergeEnqueue() work aborts after stop or
restart. Ensure both full-stop and partial-start paths perform the complete
idempotent cleanup, including runtime, research, reconciler, and tunnel
resources, only after deferred startup settles.
- Around line 943-946: Replace the dynamic import in the project-engine
initialization flow with a static top-level import from `@fusion/core`, including
AutomationStore and the sync helpers used by this module. Update the surrounding
logic to use those imported symbols directly while preserving the existing
initialization behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f020dc62-3b3c-47b9-b679-d07d274a0b4e
📒 Files selected for processing (12)
.changeset/faster-startup-p0.mddocs/plans/2026-07-14-001-feat-faster-startup-plan.mdpackages/cli/src/__tests__/startup-phase.test.tspackages/cli/src/commands/__tests__/serve.test.tspackages/cli/src/commands/dashboard.tspackages/cli/src/commands/serve.tspackages/cli/src/startup-phase.tspackages/core/src/postgres/startup-factory.tspackages/engine/src/__tests__/project-engine-deferred-startup.test.tspackages/engine/src/__tests__/project-engine-manager.test.tspackages/engine/src/project-engine-manager.tspackages/engine/src/project-engine.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/cli/src/startup-phase.ts
- .changeset/faster-startup-p0.md
- packages/cli/src/tests/startup-phase.test.ts
- packages/engine/src/tests/project-engine-manager.test.ts
- packages/core/src/postgres/startup-factory.ts
- packages/cli/src/commands/serve.ts
- packages/engine/src/tests/project-engine-deferred-startup.test.ts
- packages/cli/src/commands/dashboard.ts
🛑 Comments failed to post (5)
docs/plans/2026-07-14-001-feat-faster-startup-plan.md (1)
277-295: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Add an explicit stop-aware cancellation requirement.
The plan fire-and-forgets deferred startup work, but the PR objective requires deferred work not to resume after
stop(). Add a generation/abort guard before each deferred phase and a test that stops the engine before the chain runs; otherwise background work may touch closed stores or services.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/plans/2026-07-14-001-feat-faster-startup-plan.md` around lines 277 - 295, Add explicit stop-aware cancellation to the deferred startup chain initiated by ProjectEngine.start: track a generation or abort signal, validate it before each deferred phase, and prevent any phase from running after stop(). Add coverage that stops the engine before the deferred chain executes and verifies no deferred work resumes or accesses closed services.packages/cli/src/commands/__tests__/serve.test.ts (1)
700-728: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve per-project store isolation and failed-start retries in this fake.
{ ...options }passes the cwdexternalTaskStoreto every project, unlike production’s root gating. A rejected promise also remains instartingforever. Omit the shared store for nonmatching roots and deletestartinginfinally.As per coding guidelines, bug regression tests must assert the general invariant across all known surfaces.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/commands/__tests__/serve.test.ts` around lines 700 - 728, Update ensureEngine so each project receives externalTaskStore only when its root matches the configured project root, omitting the shared store for nonmatching projects to preserve isolation. Ensure the starting entry is removed in a finally block regardless of whether engine.start succeeds or rejects, allowing failed starts to retry. Add regression assertions covering this invariant across all relevant project surfaces.Source: Coding guidelines
packages/engine/src/project-engine.ts (3)
500-506: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Join deferred startup before tearing down the engine.
stop()can run while a deferred subsystem’sstart()is awaiting; cleanup then runs too early, and that subsystem may install its timer afterward. OldstartupMergeEnqueue()work can also resume after stop/restart because it lacks the generation guard. The partial-start branch additionally skips runtime, research, reconciler, and tunnel cleanup.Retain the deferred promise, invalidate its generation, await settlement, then perform complete idempotent cleanup.
Also applies to: 1020-1025, 1050-1097, 1193-1197, 1211-1256, 4858-4868
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/project-engine.ts` around lines 500 - 506, Update stop() to retain and await any in-flight deferred subsystem startup before tearing down the engine, while invalidating startupGeneration so resumed startupMergeEnqueue() work aborts after stop or restart. Ensure both full-stop and partial-start paths perform the complete idempotent cleanup, including runtime, research, reconciler, and tunnel resources, only after deferred startup settles.
679-700: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
Do not release the merge latch while its body can still mutate state.
The timeout and shutdown paths set
mergeBodyInFlighttonullwithout terminating the underlying merge. A subsequent merge can then run concurrently with the orphan and race repository refs or persisted merge state. Keep the latch until actual settlement, or isolate the body in a supervised, killable process.Also applies to: 1259-1264
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/project-engine.ts` around lines 679 - 700, The timeout path in awaitPriorMergeBodySettle must not clear mergeBodyInFlight while the prior merge is still running; retain the latch until the underlying promise actually settles, and apply the same correction to the shutdown handling around the merge-body cleanup at the referenced later logic. Ensure subsequent merges cannot start concurrently with an orphaned merge body.
943-946: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail # Show file size and relevant sections wc -l packages/engine/src/project-engine.ts sed -n '1,140p' packages/engine/src/project-engine.ts printf '\n--- around 920-980 ---\n' sed -n '920,980p' packages/engine/src/project-engine.ts printf '\n--- search `@fusion/core` usage in file ---\n' rg -n '"`@fusion/core`"|AutomationStore|setCreateFnAgent|import\("`@fusion/core`"\)' packages/engine/src/project-engine.ts printf '\n--- search for static `@fusion/core` imports in nearby engine files ---\n' rg -n '^import .*from "`@fusion/core`"|^import .*from '\''`@fusion/core`'\''' packages/engine/src -g '*.ts'Repository: Runfusion/Fusion
Length of output: 50372
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '--- locate AutomationStore in `@fusion/core` ---\n' rg -n "class AutomationStore|export .*AutomationStore|AutomationStore" packages/core packages/engine -g '*.ts' | sed -n '1,160p' printf '\n--- inspect core files that define/export AutomationStore ---\n' auto_file="$(rg -l "class AutomationStore|export .*AutomationStore" packages/core -g '*.ts' | head -n 1)" printf 'file=%s\n' "$auto_file" if [ -n "$auto_file" ]; then wc -l "$auto_file" sed -n '1,220p' "$auto_file" fi printf '\n--- search for imports from packages/engine into packages/core ---\n' rg -n 'from "\.\./\.\./engine|from "\.\./engine|from "\./.*project-engine|from "\./.*project-runtime|from "`@fusion/engine`"' packages/core -g '*.ts' | sed -n '1,200p' printf '\n--- search for any core->engine direct dependency patterns around automation store ---\n' rg -n 'AutomationStore|CronRunner|schedule' packages/core -g '*.ts' | sed -n '1,220p'Repository: Runfusion/Fusion
Length of output: 245
🏁 Script executed:
#!/bin/bash set -euo pipefail echo '--- locate AutomationStore in `@fusion/core` ---' rg -n "class AutomationStore|export .*AutomationStore|AutomationStore" packages/core -g '*.ts' | sed -n '1,200p' echo echo '--- inspect core file(s) that define/export AutomationStore ---' auto_file="$(rg -l "class AutomationStore|export .*AutomationStore" packages/core -g '*.ts' | head -n 1)" echo "file=$auto_file" if [ -n "$auto_file" ]; then wc -l "$auto_file" sed -n '1,240p' "$auto_file" fi echo echo '--- search for packages/core importing packages/engine ---' rg -n 'from "\.\./\.\./engine|from "\.\./engine|from "`@fusion/engine`"|from "\.\/.*project-engine|from "\.\/.*project-runtime' packages/core -g '*.ts' | sed -n '1,200p' echo echo '--- search for engine-specific terms in core ---' rg -n 'AutomationStore|CronRunner|schedule' packages/core -g '*.ts' | sed -n '1,200p'Repository: Runfusion/Fusion
Length of output: 23936
🏁 Script executed:
#!/bin/bash set -euo pipefail echo '--- core -> engine imports ---' rg -n 'from "\.\./\.\./engine|from "\.\./engine|from "`@fusion/engine`"|from "\./.*project-engine|from "\./.*project-runtime' packages/core/src -g '*.ts' || true echo echo '--- engine -> core dynamic imports in project-engine.ts ---' rg -n 'await import\("`@fusion/core`"\)|from "`@fusion/core`"' packages/engine/src/project-engine.ts | sed -n '1,120p' echo echo '--- inspect core automation-store imports ---' sed -n '1,120p' packages/core/src/automation-store.ts echo echo '--- inspect core eval-automation import style ---' sed -n '1,90p' packages/core/src/eval-automation.tsRepository: Runfusion/Fusion
Length of output: 8771
🏁 Script executed:
#!/bin/bash set -euo pipefail sed -n '1038,1068p' packages/engine/src/project-engine.ts echo rg -n 'coreAutomationModule' packages/engine/src/project-engine.tsRepository: Runfusion/Fusion
Length of output: 2545
Use a static
@fusion/coreimport. ImportAutomationStoreand the sync helpers statically instead ofawait import("@fusion/core"); this package edge should stay statically analyzable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/project-engine.ts` around lines 943 - 946, Replace the dynamic import in the project-engine initialization flow with a static top-level import from `@fusion/core`, including AutomationStore and the sync helpers used by this module. Update the surrounding logic to use those imported symbols directly while preserving the existing initialization behavior.Source: Coding guidelines
Summary
Speeds up time-to-HTTP-ready for
fn dashboardandfn serveafter the PostgreSQL cutover without reintroducing the historical 3s cwd-engine race that degraded webhooks.TaskStoreasexternalTaskStoreso cwdensureEnginedoes not open a second pool; share only when store root matches project working directory (multi-project safe).startAll()before listen; await only the primary engine; background the rest + reconciliation.merging/merging-prbefore ready so manual merge is not blocked after crash.--paused: applyenginePausedbeforeensureEngine/startAll(dashboard ordering).stop()clearsshuttingDown.phaseTimehelper, factory substep logs, serve time-to-listen.Plan:
docs/plans/2026-07-14-001-feat-faster-startup-plan.mdTest plan
packages/engine—project-engine-manager.test.ts(path-matched external store)packages/engine—project-engine-deferred-startup.test.ts(status clear, OAuth order, stop generation)packages/cli—startup-phase.test.tspackages/cli—serve.test.ts(60 tests, including--paused)fn dashboard/fn serveand comparestartup phase */time-to-listenlogspnpm smoke:boot(real serve/api/healthon ephemeral port)Summary by CodeRabbit
Performance
Reliability
Diagnostics
Tests