Skip to content

fix: report engine available when another fusion process owns it - #1704

Merged
gsxdsm merged 5 commits into
mainfrom
gsxdsm/engine-not-running
Jun 21, 2026
Merged

fix: report engine available when another fusion process owns it#1704
gsxdsm merged 5 commits into
mainfrom
gsxdsm/engine-not-running

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

What & why

Starting the dashboard with pnpm dev dashboard --no-auth showed an "engine not running" banner even though an engine was running. The banner is wrong, not the engine.

The engine uses a per-machine singleton lock (engine-singleton-lock.ts): only one fusion process may run the engine for a given project. When a second process launches (a dev dashboard alongside an already-running fusion, a leftover pnpm fusion, a daemon, etc.), it is correctly refused the lock with EngineAlreadyRunningError — which is positive proof an engine is live.

The bug: that proof was thrown away. ProjectEngineManager.createAndStart logged "Refusing to start" and rethrew without recording that an engine exists, and the dashboard health check hasDashboardEngine only counted engines this process owns (getAllEngines()). Result: /api/health reported engine.available: false → banner. Reconciliation then retried every 30s, re-colliding and re-logging each tick.

Surfaced by feat: start engines by default (#1699), which made the dashboard actively attempt engine startup.

Changes

  • ProjectEngineManager tracks an externalEngines set — projects whose lock is owned by another process — populated on EngineAlreadyRunningError, cleared when this process takes over and in stopAll(). New hasRunningEngine() and getExternalEngineIds().
  • The "refusing to start" warning now fires once per project, not on every reconciliation tick.
  • hasDashboardEngine (dashboard server.ts) consults hasRunningEngine() (with a fallback for older managers / test doubles), so the banner reflects machine-level truth. Reconciliation still retries, so this process takes over if the lock-holder exits.

Tests

  • project-engine-manager.test.ts: reports a running engine when the lock is held elsewhere; logs the refusal only once across repeated attempts; takes over and clears the external marker once the lock frees.
  • server.test.ts: /api/health reports available: true when another process owns the engine.

These paths had no prior coverage — tests assumed the manager always owns what it starts.

Verification

  • engine: 39/39 pass, tsc --noEmit clean
  • engine-singleton-lock: 8/8 pass
  • dashboard: 109/109 pass

Open in Stage

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed a false “engine not running” banner when an engine is already active under a different Fusion process on the same machine.
    • Updated dashboard /api/health so engine availability reflects externally running engines, not only engines owned by the current process.
  • Improvements

    • Reduced log noise by deduplicating “refusing to start” and avoiding repeated reconciliation warnings during external-engine scenarios.
  • Documentation

    • Added troubleshooting guidance for “engine already running” cases, including how the singleton lock affects availability reporting.

The dashboard's engine-availability health check only counted engines
this process started. A second launch (e.g. `pnpm dev dashboard`
alongside an already-running `fusion`) is correctly refused the
per-machine engine singleton lock, so its engine map stays empty and
the dashboard showed a false "engine not running" banner even though an
engine was live on the machine.

ProjectEngineManager now records projects whose singleton lock is held
by another process (via EngineAlreadyRunningError) and exposes
hasRunningEngine(), which the health endpoint consults so the banner
reflects machine-level truth. Reconciliation still retries so this
process takes over if the other exits, and the "refusing to start" log
fires once per project instead of on every 30s reconciliation tick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ghost

ghost commented Jun 21, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Document the Engine Singleton Lock concept
2 Track external engines in ProjectEngineManager
3 Update dashboard health to reflect machine truth
4 Verify engine availability and log deduplication
5 Add changeset for engine banner fix
Open in Stage

Chapters generated by Stage for commit cb64e87 on Jun 21, 2026 10:35am UTC.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a3d7cc3-72d7-4922-aa3a-c40d8e4327a7

📥 Commits

Reviewing files that changed from the base of the PR and between 0a49023 and cb64e87.

📒 Files selected for processing (3)
  • docs/solutions/integration-issues/engine-already-running-is-not-no-engine.md
  • packages/engine/src/__tests__/project-engine-manager.test.ts
  • packages/engine/src/project-engine-manager.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/solutions/integration-issues/engine-already-running-is-not-no-engine.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/engine/src/tests/project-engine-manager.test.ts
  • packages/engine/src/project-engine-manager.ts

📝 Walkthrough

Walkthrough

ProjectEngineManager gains an externalEngines set that records projects whose singleton lock is held by another Fusion process. Two new public methods (hasRunningEngine, getExternalEngineIds) expose this state. Startup, reconciliation, and shutdown paths now treat EngineAlreadyRunningError as expected (swallowing it to avoid repeated warnings). The dashboard's hasDashboardEngine function prefers hasRunningEngine() when available, falling back to raw engine-set size for backward compatibility. Tests verify cross-process ownership detection, state transitions, and warning deduplication. Documentation explains the problem, solution, and operational implications.

Changes

Engine banner fix

Layer / File(s) Summary
ProjectEngineManager external ownership tracking and public API
packages/engine/src/project-engine-manager.ts
Adds private externalEngines set to track projects owned externally; introduces hasRunningEngine() and getExternalEngineIds() public methods; createAndStart() records a project in externalEngines on lock-acquisition failure, logs the refusal warning only once per project, and removes it immediately after acquiring the lock.
ProjectEngineManager error handling in startup, reconciliation, and shutdown
packages/engine/src/project-engine-manager.ts
startAll() skips EngineAlreadyRunningError instead of counting it as a failure; stopAll() clears externalEngines to prevent stale external availability; onProjectAccessed() swallows the error without warning; reconcile() swallows the error to prevent repeated warnings across ticks.
Dashboard health check update, server tests, and changeset
packages/dashboard/src/server.ts, packages/dashboard/src/__tests__/server.test.ts, .changeset/engine-not-running-banner.md
hasDashboardEngine branches on engineManager.hasRunningEngine() when available, falling back to getAllEngines() size; two new /api/health tests cover the cross-process ownership scenario and backward compatibility; changeset documents the patch.
ProjectEngineManager external-ownership test suite
packages/engine/src/__tests__/project-engine-manager.test.ts
Imports acquireEngineSingleton and EngineAlreadyRunningError for deterministic mocking; adds a describe block verifying rejection on lock failure, warning deduplication across repeated attempts and reconciliation ticks, state transitions after lock acquisition, suppression of reconciliation warnings, and marker cleanup on partial startup failure.
Solution guide and concept definition
docs/solutions/integration-issues/engine-already-running-is-not-no-engine.md, CONCEPTS.md
New solution document describes the problem (false "engine not running" banner despite active engines), root cause (prior health check only reflected process-local engines), technical resolution (track external ownership, suppress per-project warnings, surface via hasRunningEngine(), clear on takeover and shutdown), prevention guidance, test coverage summary, and operational gotcha for orphaned lock holders; CONCEPTS.md defines the Engine Singleton Lock mechanism.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A banner once lied, "No engine around!"
But another wee process had already been found.
With externalEngines set, the truth came to light—
hasRunningEngine() shining like stars in the night.
The warning logs once, the dashboard stays true,
No more false alarms—just the facts coming through! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 pull request title accurately and concisely summarizes the main change: fixing the false "engine not running" banner by reporting engine availability when another Fusion process owns it.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gsxdsm/engine-not-running

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a false "engine not running" banner by teaching ProjectEngineManager to distinguish between "no engine on this machine" and "an engine is running but owned by another process." When acquireEngineSingleton throws EngineAlreadyRunningError, the manager now records the project in an externalEngines set and exposes hasRunningEngine(), which the dashboard health endpoint uses instead of the process-local getAllEngines().

  • ProjectEngineManager: adds externalEngines set, hasRunningEngine(), and getExternalEngineIds(); deduplicates the "refusing to start" warning; clears the external marker on successful lock acquisition and in stopAll(); fire-and-forget callers (reconcile, startAll, onProjectAccessed) suppress the expected error silently.
  • server.ts: hasDashboardEngine calls hasRunningEngine() when available, falling back to getAllEngines() for older manager instances or test doubles.
  • Tests: new coverage for external-engine detection, log deduplication, takeover on lock-free, and failure-during-takeover cleanup in both the engine and dashboard packages.

Confidence Score: 3/5

Mostly safe — the core bug fix is correct and well-tested, but pauseProject does not clear externalEngines, which can leave a stale entry after the external lock-holder exits, reproducing the false banner via the pause path.

pauseProject removes a project from this.starting and releases the singleton lock, but never calls this.externalEngines.delete(projectId). Reconciliation explicitly skips paused projects, so no periodic retry will ever evict the stale entry. If the external lock-holder exits while the project is paused, hasRunningEngine() continues to return true — the exact false-positive banner condition this PR set out to fix, now reachable through the pause path.

packages/engine/src/project-engine-manager.ts — specifically the pauseProject method, which needs this.externalEngines.delete(projectId) to match the cleanup done in stopAll() and at lock-acquisition time.

Important Files Changed

Filename Overview
packages/engine/src/project-engine-manager.ts Adds externalEngines set and hasRunningEngine() / getExternalEngineIds() to track externally-owned engines; pauseProject is missing the corresponding externalEngines.delete cleanup, leaving a stale entry that can cause a false-positive hasRunningEngine() after the external process exits.
packages/dashboard/src/server.ts Updates hasDashboardEngine to call hasRunningEngine() when available, with a clean fallback to getAllEngines(); logic is correct and backward-compatible.
packages/engine/src/tests/project-engine-manager.test.ts Adds five targeted tests for the external-engine path; covers deduplication of warning logs, takeover on lock-free, reconciliation quietness, and failure-during-takeover cleanup. No test for the pause + external-engine combination.
packages/dashboard/src/tests/server.test.ts Adds health-endpoint tests for externally-owned engine and legacy fallback path; both cases are well-exercised.
.changeset/engine-not-running-banner.md Correct patch-level changeset; description accurately captures the bug and the fix.
CONCEPTS.md Adds a concise, accurate definition of the Engine Singleton Lock concept.
docs/solutions/integration-issues/engine-already-running-is-not-no-engine.md Detailed post-mortem doc covering symptoms, root cause, solution, and prevention; well-structured and accurate.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant D as Dashboard Process
    participant PEM as ProjectEngineManager
    participant SL as Engine Singleton Lock
    participant EP as External (Owner) Process
    participant UI as /api/health

    EP->>SL: acquireEngineSingleton(projectId) ✓ (owns lock)

    D->>PEM: startAll() / reconcile()
    PEM->>SL: acquireEngineSingleton(projectId)
    SL-->>PEM: EngineAlreadyRunningError

    Note over PEM: externalEngines.add(projectId)<br/>log warning once
    PEM-->>D: throws EngineAlreadyRunningError (swallowed)

    D->>UI: GET /api/health
    UI->>PEM: hasRunningEngine()
    PEM-->>UI: "true (externalEngines.size > 0)"
    UI-->>D: "{ engine: { available: true } }"

    Note over EP: External process exits, releases lock

    D->>PEM: "reconcile() — retries because has(projectId)=false"
    PEM->>SL: acquireEngineSingleton(projectId) ✓ (lock now free)
    Note over PEM: externalEngines.delete(projectId)
    PEM->>PEM: engine.start()
    PEM->>PEM: engines.set(projectId, engine)
    Note over PEM: This process now owns the engine
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant D as Dashboard Process
    participant PEM as ProjectEngineManager
    participant SL as Engine Singleton Lock
    participant EP as External (Owner) Process
    participant UI as /api/health

    EP->>SL: acquireEngineSingleton(projectId) ✓ (owns lock)

    D->>PEM: startAll() / reconcile()
    PEM->>SL: acquireEngineSingleton(projectId)
    SL-->>PEM: EngineAlreadyRunningError

    Note over PEM: externalEngines.add(projectId)<br/>log warning once
    PEM-->>D: throws EngineAlreadyRunningError (swallowed)

    D->>UI: GET /api/health
    UI->>PEM: hasRunningEngine()
    PEM-->>UI: "true (externalEngines.size > 0)"
    UI-->>D: "{ engine: { available: true } }"

    Note over EP: External process exits, releases lock

    D->>PEM: "reconcile() — retries because has(projectId)=false"
    PEM->>SL: acquireEngineSingleton(projectId) ✓ (lock now free)
    Note over PEM: externalEngines.delete(projectId)
    PEM->>PEM: engine.start()
    PEM->>PEM: engines.set(projectId, engine)
    Note over PEM: This process now owns the engine
Loading

Comments Outside Diff (1)

  1. packages/engine/src/project-engine-manager.ts, line 156-177 (link)

    P1 pauseProject does not clear externalEngines. If another process holds the engine lock for a project that is then paused, the stale entry survives in externalEngines indefinitely. Reconciliation explicitly filters out paused projects (activeProjects.filter((p) => p.status !== "paused")), so no retry will ever evict the entry — and hasRunningEngine() keeps returning true after the external holder exits, producing the same false-positive banner this PR is designed to eliminate.

    Add this.externalEngines.delete(projectId) after this.starting.delete(projectId) in pauseProject.

Reviews (3): Last reviewed commit: "docs: enrich engine singleton-lock learn..." | Re-trigger Greptile

…or != no engine)

Document the false "engine not running" banner root cause and fix as a
docs/solutions learning, and add an "Engine Singleton Lock" entry to
CONCEPTS.md: a failed per-machine lock acquisition is proof an engine is
running elsewhere, not "no engine."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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

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-manager.ts (1)

468-488: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear stale external ownership as soon as the singleton is acquired.

If a previous attempt marked the project external, then the other process exits, acquireEngineSingleton() succeeds, but engine.start() fails, Line 488 never runs and hasRunningEngine() keeps reporting an engine that no longer exists.

🐛 Proposed fix to clear the marker after singleton acquisition
     });
     this.singletonLocks.set(projectId, singleton);
+    // Acquiring the singleton proves any previously observed external owner is gone.
+    this.externalEngines.delete(projectId);
 
     const engine = new ProjectEngine(
       runtimeConfig,
       this.centralCore,
       engineOptions,
@@
 
     this.engines.set(projectId, engine);
     this.starting.delete(projectId);
-    // We now own the engine — clear any prior "owned by another process" marker.
-    this.externalEngines.delete(projectId);
     runtimeLog.log(
       `Started engine for ${project.name ?? projectId} (${projectId})`,
     );
🤖 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-manager.ts` around lines 468 - 488, The
externalEngines marker is cleared after a successful engine.start() call, but if
engine.start() fails and throws an error, the marker never gets cleared, causing
hasRunningEngine() to report stale external ownership. Move the
this.externalEngines.delete(projectId) call to immediately after
this.singletonLocks.set(projectId, singleton) is executed, so the marker is
cleared as soon as the singleton is acquired, before any engine startup attempt.
This ensures that even if the subsequent engine.start() call fails, the stale
external ownership marker will not persist.
🤖 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 `@packages/dashboard/src/__tests__/server.test.ts`:
- Around line 418-435: Add a new regression test that covers the legacy fallback
branch where the hasRunningEngine method is not available on the engineManager.
Create a test case similar to "reports the engine available when another fusion
process owns it" but instead remove the hasRunningEngine mock entirely and set
getAllEngines to return a non-empty Map (indicating a running engine exists).
Verify that the GET request to "/api/health" still returns status 200 with
engine.available set to true. This ensures the backward-compat fallback logic in
hasDashboardEngine works correctly when the newer hasRunningEngine method is
missing from the engineManager.

In `@packages/dashboard/src/server.ts`:
- Around line 485-489: The comment block explaining the hasRunningEngine
behavior starting around line 485 needs to be converted to follow the FNXC
requirement-trace format for consistency with repository coding guidelines. Add
a FNXC-formatted header comment preceding the existing behavior explanation
using the format FNXC:<Area-of-product> yyyy-MM-dd-hh:mm: (replacing the
placeholders with appropriate values), and keep the existing descriptive comment
about how hasRunningEngine counts both owned and detected fusion process engines
as the body of the requirement trace. This ensures the behavior documentation
follows the repo's standard for requirement history tracking.

In `@packages/engine/src/__tests__/project-engine-manager.test.ts`:
- Around line 747-770: The current test only exercises the direct ensureEngine()
call path and verifies that the refusal warning is logged once, but it does not
test the reconciliation wrapper that periodically attempts to start the engine.
Modify this test to drive the reconciliation path by using vi.useFakeTimers() to
simulate time passing and allow the reconciliation mechanism to trigger multiple
intervals, then verify that both the "Refusing to start engine for proj_aaa"
warning and any "Failed to start engine..." warning from the outer
reconciliation layer remain deduplicated and logged only once. Use fake timer
advancement (vi.advanceTimersByTime) instead of real polling to meet coding
guidelines for regression tests.

In `@packages/engine/src/project-engine-manager.ts`:
- Around line 54-62: Add the required FNXC requirement trace to the
externalEngines field documentation. In the JSDoc comment block for the
externalEngines Set, include an FNXC comment in the format
FNXC:<Area-of-product> yyyy-MM-dd-hh:mm: to document this user-facing behavior
change regarding dashboard banner reporting. Apply the same FNXC trace addition
to the related code block at lines 122-128. The FNXC comment should be placed
within the existing documentation to mark this as an important UX/behavior
requirement.
- Around line 455-464: The EngineAlreadyRunningError is being rethrown even
after being identified as an external engine, causing the outer reconciliation
catch block to emit a "Failed to start engine..." warning on every interval.
After logging the "Refusing to start engine..." message and recording the
projectId in the externalEngines set within the deduplication check, do not
rethrow the error. Instead, return early from the current operation or use early
exit logic so that the exception does not propagate to the outer catch block
that treats it as an unexpected failure requiring a warning on each
reconciliation tick.

---

Outside diff comments:
In `@packages/engine/src/project-engine-manager.ts`:
- Around line 468-488: The externalEngines marker is cleared after a successful
engine.start() call, but if engine.start() fails and throws an error, the marker
never gets cleared, causing hasRunningEngine() to report stale external
ownership. Move the this.externalEngines.delete(projectId) call to immediately
after this.singletonLocks.set(projectId, singleton) is executed, so the marker
is cleared as soon as the singleton is acquired, before any engine startup
attempt. This ensures that even if the subsequent engine.start() call fails, the
stale external ownership marker will not persist.
🪄 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: 4b2bfa92-01f7-4e39-903d-5ad47a7c082f

📥 Commits

Reviewing files that changed from the base of the PR and between 4f9c4cf and 7635ba8.

📒 Files selected for processing (5)
  • .changeset/engine-not-running-banner.md
  • packages/dashboard/src/__tests__/server.test.ts
  • packages/dashboard/src/server.ts
  • packages/engine/src/__tests__/project-engine-manager.test.ts
  • packages/engine/src/project-engine-manager.ts

Comment thread packages/dashboard/src/__tests__/server.test.ts
Comment thread packages/dashboard/src/server.ts Outdated
Comment thread packages/engine/src/__tests__/project-engine-manager.test.ts
Comment thread packages/engine/src/project-engine-manager.ts
Comment thread packages/engine/src/project-engine-manager.ts
gsxdsm and others added 2 commits June 21, 2026 03:29
- Stop reconciliation/startAll/onProjectAccessed from warning every tick for
  externally-owned engines: swallow EngineAlreadyRunningError in the outer
  catches (it's expected and already logged once in createAndStart)
- Add FNXC:DashboardHealth requirement-trace comments on the externalEngines
  field and the dashboard hasRunningEngine health check
- Add regression test: reconciliation stays quiet across ticks for an
  externally-owned engine (inner refusal logged once, outer failure suppressed)
- Add regression test: hasDashboardEngine legacy fallback to getAllEngines when
  hasRunningEngine is unavailable on the manager

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…quire

Move externalEngines.delete(projectId) to immediately after acquiring the
singleton lock instead of after engine.start() succeeds. If a project was
marked external, the holder exits, acquire succeeds, but start() then throws,
the success-path delete never ran and hasRunningEngine() reported a phantom
engine forever. Added a regression test for the failed-takeover path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gsxdsm

gsxdsm commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Outside diff range: Clear stale external ownership as soon as the singleton is acquired. If a previous attempt marked the project external, the other process exits, acquireEngineSingleton() succeeds, but engine.start() fails, the delete never runs and hasRunningEngine() keeps reporting an engine that no longer exists.

Addressed in 0c… (latest commit): moved this.externalEngines.delete(projectId) to immediately after this.singletonLocks.set(projectId, singleton), before engine.start(). Removed the now-redundant delete on the success path. Added a regression test (clears the external marker even if the takeover start fails) that asserts hasRunningEngine() === false and the marker is cleared when a takeover acquire succeeds but engine.start() throws.

- Fix frontmatter enums (root_cause: logic_error, component: tooling); add #1699
  to related_prs and last_updated
- Reflect post-review behavior: external marker cleared at lock-acquire time;
  reconcile/startAll/onProjectAccessed swallow EngineAlreadyRunningError
- Add rejected approach (don't merge has()/hasRunningEngine) and the
  keep-them-distinct invariant
- Tighten socket-name detail (sha1[:16]); cross-link the browser-testing doc

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gsxdsm
gsxdsm merged commit 70653dd into main Jun 21, 2026
6 checks passed
@gsxdsm
gsxdsm deleted the gsxdsm/engine-not-running branch July 24, 2026 06:08
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