Skip to content

feat: faster dashboard and serve startup#2132

Merged
gsxdsm merged 6 commits into
mainfrom
feature/faster-startup
Jul 15, 2026
Merged

feat: faster dashboard and serve startup#2132
gsxdsm merged 6 commits into
mainfrom
feature/faster-startup

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Speeds up time-to-HTTP-ready for fn dashboard and fn serve after the PostgreSQL cutover without reintroducing the historical 3s cwd-engine race that degraded webhooks.

  • Dashboard store share (serve parity): inject the factory-booted TaskStore as externalTaskStore so cwd ensureEngine does not open a second pool; share only when store root matches project working directory (multi-project safe).
  • Serve multi-project: stop awaiting startAll() before listen; await only the primary engine; background the rest + reconciliation.
  • Defer non-route-critical engine work: ordered OAuth (refresh → monitor), automation schedule syncs, and auto-merge enqueue after the engine handle is returnable.
  • Critical-path merge status clear: still clear stale merging/merging-pr before ready so manual merge is not blocked after crash.
  • Serve --paused: apply enginePaused before ensureEngine/startAll (dashboard ordering).
  • Stop safety: generation counter so deferred tails cannot resume after stop() clears shuttingDown.
  • Phase timing: shared phaseTime helper, factory substep logs, serve time-to-listen.

Plan: docs/plans/2026-07-14-001-feat-faster-startup-plan.md

Test plan

  • packages/engineproject-engine-manager.test.ts (path-matched external store)
  • packages/engineproject-engine-deferred-startup.test.ts (status clear, OAuth order, stop generation)
  • packages/clistartup-phase.test.ts
  • packages/cliserve.test.ts (60 tests, including --paused)
  • Local: warm fn dashboard / fn serve and compare startup phase * / time-to-listen logs
  • pnpm smoke:boot (real serve /api/health on ephemeral port)

Summary by CodeRabbit

  • Performance

    • Improved dashboard and serve startup times, including faster time-to-listen and time-to-ready.
    • Moved non-essential background initialization off the critical startup path.
    • Parallelized dashboard service initialization where possible.
  • Reliability

    • Improved multi-project startup handling and project selection.
    • Prevented cross-project task-store sharing.
    • Added safer shutdown behavior for partially completed startup.
  • Diagnostics

    • Added startup phase timing logs to help identify performance bottlenecks.
  • Tests

    • Expanded coverage for deferred startup, shutdown, project isolation, and startup timing.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@gsxdsm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 46208f3f-03a7-4350-b5b2-03072cc63251

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6753b and c30af2b.

📒 Files selected for processing (5)
  • .changeset/friendly-shims-fix.md
  • packages/cli/src/__tests__/plugin-sdk-export.test.ts
  • packages/cli/src/plugin-sdk-core-runtime-shim.mjs
  • packages/cli/src/plugin-sdk-core-runtime-shim.ts
  • packages/cli/tsup.config.ts
📝 Walkthrough

Walkthrough

Dashboard 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.

Changes

Faster startup flow

Layer / File(s) Summary
Startup timing and bootstrap instrumentation
.changeset/*, docs/plans/..., packages/cli/src/startup-phase.ts, packages/cli/src/commands/dashboard.ts, packages/core/src/postgres/startup-factory.ts
Adds phaseTime logging and applies it to dashboard, serve, PostgreSQL backend creation, extension loading, and satellite initialization.
Dashboard shared store and cwd engine readiness
packages/cli/src/commands/dashboard.ts, packages/engine/src/project-engine-manager.ts, packages/engine/src/__tests__/project-engine-manager.test.ts
Reuses the dashboard TaskStore for matching project roots and fully awaits cwd engine creation before server setup.
Serve readiness and primary engine selection
packages/cli/src/commands/serve.ts, packages/cli/src/commands/__tests__/serve.test.ts
Runs background engine startup, starts reconciliation earlier, ensures the selected primary engine, preserves pause state, and logs time-to-listen.
Deferred ProjectEngine work and shutdown cancellation
packages/engine/src/project-engine.ts, packages/engine/src/__tests__/project-engine-deferred-startup.test.ts
Defers selected notification, OAuth, automation, cron, and merge work with generation guards and partial cleanup during shutdown.

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
Loading

Possibly related PRs

  • Runfusion/Fusion#1699: Both changes modify dashboard engine initialization and engine-mode startup behavior.
  • Runfusion/Fusion#2108: Both changes modify PostgreSQL TaskStore and CentralCore ownership in dashboard and serve startup.
  • Runfusion/Fusion#2109: Both changes modify ProjectEngine startup and shared PostgreSQL wiring.

Suggested reviewers: plarson

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: faster startup for dashboard and serve.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/faster-startup

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR shortens dashboard and serve startup time. The main changes are:

  • Shares the cwd PostgreSQL task store with its engine.
  • Starts secondary project engines after the HTTP server begins listening.
  • Defers notifier, OAuth, automation, and merge-enqueue work.
  • Adds shutdown guards and startup phase timing.
  • Bundles PostgreSQL schema source through a runtime shim.

Confidence Score: 5/5

This looks safe to merge.

  • The latest fixes preserve notifier and OAuth ordering while isolating startup failures.
  • Cron starts only after persisted schedules are synchronized.
  • Shared stores now match canonical project roots.
  • No new blocking issue qualifies for this follow-up review.

Important Files Changed

Filename Overview
packages/engine/src/project-engine.ts Splits engine startup into critical and deferred phases while preserving ordering and shutdown guards.
packages/engine/src/project-engine-manager.ts Restricts shared task stores to projects with matching canonical roots.
packages/cli/src/commands/serve.ts Warms the primary engine before listening and starts other project engines in the background.
packages/cli/src/commands/dashboard.ts Shares the dashboard task store, parallelizes satellite initialization, and adds phase timing.
packages/cli/src/plugin-sdk-core-runtime-shim.mjs Bundles PostgreSQL schema runtime values from source without requiring a prebuilt core artifact.

Reviews (4): Last reviewed commit: "fix(ci): bundle plugin schema shim from ..." | Re-trigger Greptile

Comment thread packages/engine/src/project-engine.ts Outdated
Comment thread packages/engine/src/project-engine.ts Outdated
Comment thread packages/engine/src/project-engine-manager.ts
Comment thread packages/engine/src/project-engine.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Partial-start stop() cleanup never resets shuttingDown, 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.started early-return branch does not — it returns at line 1146 having only set this.shuttingDown = true at the top of stop().

Once this branch executes on an engine instance (e.g. stop() called while start() failed/was still in-flight before reaching this.started = true), shuttingDown is stuck true forever on that object. Every future call on the same instance is affected:

  • deferredStartupAborted() (line 939) short-circuits on this.shuttingDown, so a subsequent start() on the same instance will never run notifiers, OAuth, automation sync, or start CronRunner.
  • internalEnqueueMerge() (line 2499) returns false immediately, so auto-merge on task:moved, manual onMerge, 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 exercises stop() after start() 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, call stop(), then start() 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 value

Remove unnecessary nullish coalescing.

planWorkspaceBuild is guaranteed to return skippedPackages as 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 value

Ensure cache.entries is not null to match the JSDoc return type and legacy fallback logic.

While optional chaining downstream prevents a crash, typeof null evaluates to "object". This allows the function to return { version: 2, entries: null } if a malformed file is read, which violates the Record return 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 value

Prefer 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 win

Mirror dashboard.ts's single getGlobalSettingsStore().getSettings() read here too.

dashboard.ts now resolves Claude/Droid/Llama CLI paths from one shared globalSettings read (see packages/cli/src/commands/dashboard.ts lines 1683-1715), but serve.ts still performs three separate sequential store.getGlobalSettingsStore().getSettings() calls, each on the time-to-listen critical path (this all runs before createServer/listen). Given the PR's stated goal of speeding up time-to-listen for both dashboard and serve, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c00841 and 157a03c.

📒 Files selected for processing (14)
  • .changeset/faster-startup-p0.md
  • docs/plans/2026-07-14-001-feat-faster-startup-plan.md
  • packages/cli/src/__tests__/startup-phase.test.ts
  • packages/cli/src/commands/__tests__/serve.test.ts
  • packages/cli/src/commands/dashboard.ts
  • packages/cli/src/commands/serve.ts
  • packages/cli/src/startup-phase.ts
  • packages/core/src/postgres/startup-factory.ts
  • packages/engine/src/__tests__/project-engine-deferred-startup.test.ts
  • packages/engine/src/__tests__/project-engine-manager.test.ts
  • packages/engine/src/project-engine-manager.ts
  • packages/engine/src/project-engine.ts
  • scripts/__tests__/build-workspace.test.mjs
  • scripts/build-workspace.mjs

gsxdsm added 5 commits July 15, 2026 12:38
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.
@gsxdsm gsxdsm force-pushed the feature/faster-startup branch from 157a03c to 1d6753b Compare July 15, 2026 19:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 157a03c and 1d6753b.

📒 Files selected for processing (12)
  • .changeset/faster-startup-p0.md
  • docs/plans/2026-07-14-001-feat-faster-startup-plan.md
  • packages/cli/src/__tests__/startup-phase.test.ts
  • packages/cli/src/commands/__tests__/serve.test.ts
  • packages/cli/src/commands/dashboard.ts
  • packages/cli/src/commands/serve.ts
  • packages/cli/src/startup-phase.ts
  • packages/core/src/postgres/startup-factory.ts
  • packages/engine/src/__tests__/project-engine-deferred-startup.test.ts
  • packages/engine/src/__tests__/project-engine-manager.test.ts
  • packages/engine/src/project-engine-manager.ts
  • packages/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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 157a03c and 1d6753b.

📒 Files selected for processing (12)
  • .changeset/faster-startup-p0.md
  • docs/plans/2026-07-14-001-feat-faster-startup-plan.md
  • packages/cli/src/__tests__/startup-phase.test.ts
  • packages/cli/src/commands/__tests__/serve.test.ts
  • packages/cli/src/commands/dashboard.ts
  • packages/cli/src/commands/serve.ts
  • packages/cli/src/startup-phase.ts
  • packages/core/src/postgres/startup-factory.ts
  • packages/engine/src/__tests__/project-engine-deferred-startup.test.ts
  • packages/engine/src/__tests__/project-engine-manager.test.ts
  • packages/engine/src/project-engine-manager.ts
  • packages/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 cwd externalTaskStore to every project, unlike production’s root gating. A rejected promise also remains in starting forever. Omit the shared store for nonmatching roots and delete starting in finally.

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’s start() is awaiting; cleanup then runs too early, and that subsystem may install its timer afterward. Old startupMergeEnqueue() 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 mergeBodyInFlight to null without 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.ts

Repository: 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.ts

Repository: Runfusion/Fusion

Length of output: 2545


Use a static @fusion/core import. Import AutomationStore and the sync helpers statically instead of await 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

@gsxdsm gsxdsm merged commit 05151a2 into main Jul 15, 2026
7 checks passed
@gsxdsm gsxdsm deleted the feature/faster-startup branch July 15, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant