Skip to content

Electron keeps stale project actions enabled after daemon exit and silently fails with AO daemon is not ready #2242

Description

@areycruzer

Summary

If the Electron-managed daemon exits while the desktop app is still open, the renderer notices enough to stop showing project data, but it leaves project actions such as New task and Spawn Orchestrator clickable on the stale board route. Clicking Spawn Orchestrator produces no visible error or recovery path in the app and logs an unhandled rejection: AO daemon is not ready.

This makes daemon crash/restart recovery feel broken: the user is left on an apparently usable screen, but actions silently fail and there is no obvious Restart daemon / Reconnect affordance even though the Electron preload/main layer already exposes daemon start IPC.

Reproduced on

  • Repo: AgentWrapper/agent-orchestrator
  • Branch: main
  • Commit: 77f33c28
  • Platform: macOS / Electron dev app
  • Runtime state: isolated local QA state under /tmp/agent-orchestrator-qa-20260627-230707
  • Launch env:
AO_DATA_DIR=/tmp/agent-orchestrator-qa-20260627-230707/ao-data \
AO_RUN_FILE=/tmp/agent-orchestrator-qa-20260627-230707/running.json \
AO_PORT=31021 \
AO_TELEMETRY_EVENTS=off \
AO_TELEMETRY_METRICS=off \
AO_TELEMETRY_REMOTE=off \
VITE_AO_POSTHOG_HOST=http://127.0.0.1:9 \
npm --prefix frontend run dev

Repro steps

  1. Start the Electron dev app with the isolated env above.
  2. Add/open a disposable local git project.
  3. Confirm the Electron UI is on a project board route and daemon-backed data is visible.
  4. Kill the isolated daemon process listed in AO_RUN_FILE while leaving Electron open.
  5. Wait for Electron to observe the daemon exit.
  6. Click Spawn Orchestrator on the still-visible project board.

Expected behavior

After the daemon exits, the UI should stop offering daemon-dependent actions or show a clear recovery state, for example:

  • Disable New task / Spawn Orchestrator while daemon status is stopped.
  • Show a visible Restart daemon / Reconnect action.
  • Catch action failures and render an inline/toast error instead of producing an unhandled rejection.
  • Preferably call the already-exposed daemon.start IPC when the user chooses recovery.

Actual behavior

  • The sidebar loses project data / shows the stopped state, but the project board route remains mounted.
  • New task and Spawn Orchestrator remain clickable.
  • Clicking Spawn Orchestrator gives no visible user-facing error.
  • Dev logs show an unhandled renderer rejection:
[vite] (client) [Unhandled rejection] Error: AO daemon is not ready.

Screenshots / evidence

Before clicking: stale project board still exposes New task and Spawn Orchestrator after daemon exit.

Stale project board still exposes New task and Spawn Orchestrator after daemon exit

After clicking Spawn Orchestrator: no visible app error; the failure only appears in logs.

Spawn Orchestrator remains clickable and gives no visible recovery/error UI

Local evidence files from the repro:

  • /tmp/agent-orchestrator-qa-20260627-230707/evidence/06-daemon-stopped-project-actions-still-enabled-cropped.png
  • /tmp/agent-orchestrator-qa-20260627-230707/evidence/07-daemon-stopped-spawn-click-no-feedback-cropped.png
  • /tmp/agent-orchestrator-qa-20260627-230707/logs/electron-dev-tail-after-daemon-stopped-click.log

Key log excerpt:

[vite] (client) [Unhandled rejection] Error: AO daemon is not ready.
  > spawnOrchestrator src/renderer/lib/spawn-orchestrator.ts:16:8
  > openOrchestrator src/renderer/components/SessionsBoard.tsx:107:21

Code pointers

  • frontend/src/renderer/components/SessionsBoard.tsx
    • openOrchestrator uses try/finally but has no catch, so daemon/API errors bubble into an unhandled rejection.
    • It also keeps board-level daemon-dependent actions available even after daemon status changes.
  • frontend/src/renderer/lib/spawn-orchestrator.ts
    • Throws the daemon/API error when /api/v1/orchestrators cannot run.
  • frontend/src/renderer/hooks/useDaemonStatus.ts
    • Applies stopped/ready daemon status and keeps polling, but does not surface a recovery action to the route/action layer.
  • frontend/src/preload.ts
    • Exposes daemon.start() and daemon.stop().
  • frontend/src/main.ts
    • Registers ipcMain.handle("daemon:start", () => startDaemon()), so the recovery primitive already exists.

Duplicate / overlap check

I checked current open issues and PRs before filing.

Nearby but not the same:

No exact open issue or PR was found for the stale Electron actions + unhandled AO daemon is not ready flow.

Contribution value

This is a good focused bug because it is:

  • User-visible in the real Electron app.
  • Reproducible without private credentials or GitHub writes.
  • A resilience/onboarding problem that users can hit during local dev, daemon crashes, manual kills, or app/dev-server restarts.
  • Likely fixable in one focused PR around daemon status gating, action error handling, and/or exposing a restart CTA using the existing IPC.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: mediumFix when convenient

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions