Skip to content

ao start drops cross-project sessions (single-project registration scope) — affects every restore variant #2111

@AgentWrapper

Description

@AgentWrapper

Bug

ao stop correctly kills sessions across all projects in the global config and writes them to last-stop.json. But ao start (any variant) only registers and polls a single project (the one resolved from cwd), and consequently any restore code that runs — whether reached via the interactive prompt on plain ao start, the auto-confirm ao start --restore, or via the skip-prompt ao start --no-restore (which skips both) — drops every session whose projectId doesn't match the registered one. A user who runs ao stop && ao start <anything> from one project's directory loses every active session in every other project, with no warning.

The fix has two layers: (1) the single-project registration scope at start time, and (2) the restore loop's project filter downstream of registration.

This is distinct from #1743 (which is the ao stop → ao update → ao start path losing last-stop.json entirely). Here last-stop.json is written and is consumed for the cwd project — but cross-project entries are silently filtered out.

Source: Live AO session, in-chat triage with Claude orchestrator | Reported by: @Itrytoohard
Analyzed against: 5897b4e8 (AgentWrapper/agent-orchestrator origin/main as of 2026-06-08)
Environment: macOS 14.6 arm64, Node v22.11.0, tmux runtime; two real projects (agent-orchestrator_5dce9e3fe8, agent-orchestrator_649ba24578) registered in global config
Confidence: High — captured the full stop+start log; saw 8/8 stopped vs 1/1 restored; verified resulting on-disk state matches.

Affected invocations

Invocation What happens to cross-project sessions
ao start → answer "yes" to restore prompt Dropped (same single-project filter applies)
ao start → answer "no" Not restored (expected — user opted out)
ao start --restore Dropped (auto-runs the same filtered restore)
ao start --no-restore Not restored (expected — user opted out)

The bug is scope-level, not flag-level. Every variant of ao start registers only the cwd's project.

Reproduction

  1. Have ≥1 active session in project A and ≥1 active session in project B, both registered in ~/.agent-orchestrator/config.yaml.
  2. From a shell whose cwd resolves to project A, run any of: ao stop && ao start --restore / ao stop && ao start (answer "yes" at prompt) / ao stop && ao start --no-restore (won't even prompt).
  3. Expected: all sessions from project A and project B come back (per CLAUDE.md: "Offers to restore sessions from last-stop.json — includes cross-project sessions via otherProjects field").
  4. Actual: only project A's sessions restore. Project B's sessions stay terminated / manually_killed with their runtimes gone. Daemon's lifecycle worker only polls project A — even sessions you manually ao session restore after the fact don't get lifecycle events for project B.

Concrete log from this user (ao stop && ao start --restore from project _649ba24578's directory):

$ ao stop && ao start --restore
[...]
Stop AO and 8 active session(s)? Yes
✔ Stopped 8 session(s)
  agent-orchestrator:               ao-10, ao-11, ao-12, ao-13, ao-14, ao-2, ao-orchestrator     ← 7 in project _5dce9e3fe8
  agent-orchestrator_649ba24578:    ao-2-orchestrator                                            ← 1 in project _649ba24578
[...]
Starting orchestrator for Agent Orchestrator
[...]
✔ Restored orchestrator session: ao-2-orchestrator
✔ Lifecycle project supervisor started
  1 session(s) from other projects were also stopped:                                            ← misleading: says "1", actually 7
  agent-orchestrator_649ba24578: ao-2-orchestrator                                               ← wrong project listed (cwd, not "other")

✔ Restored 1/1 session(s)

Lifecycle: supervised (polling 1 project(s): agent-orchestrator_649ba24578)                      ← only 1 project polled

Stopped 8, restored 1. The "1 session(s) from other projects were also stopped: agent-orchestrator_649ba24578: ao-2-orchestrator" line is itself bugged — _649ba24578 was the cwd project, not "other"; and the count says 1 when there were 7 cross-project sessions. The reader has no way to discover that 7 sessions were lost.

Root Cause Hypotheses

I haven't traced the code path, but the symptoms point at:

  1. ao start registers only the cwd-resolved project with the lifecycle supervisor (polling 1 project(s): agent-orchestrator_649ba24578). The single-project registration is independent of which flag is passed and happens before the restore code runs.

  2. The restore code path (whether reached via --restore, the default prompt, or anywhere else) iterates last-stop.json's sessions and filters out anything whose projectId doesn't match the registered set — so cross-project entries get dropped.

The misleading log line ("1 session(s) from other projects … ao-2-orchestrator") is a downstream bug in the same code path — the message-rendering treats the cwd-project session as the "other project" entry, suggesting an off-by-one or wrong-source-variable.

Pointer files (per CLAUDE.md's "Key Files" table): packages/cli/src/commands/start.ts + packages/cli/src/lib/running-state.ts.

Workaround

Per-session manual restore works because ao session restore <id> resolves the project from findSessionRecord(sessionId) across the entire global config:

for s in ao-10 ao-11 ao-12 ao-13 ao-14 ao-2 ao-orchestrator; do
  ao session restore "$s"
done

This rebuilds each worktree from its branch, relaunches the agent, and adds the session back. But the daemon's lifecycle worker still only polls the cwd project, so those restored sessions don't receive PR/CI/runtime updates until the next full restart from a directory that registers the right project — which itself has the same single-project scope. There is no in-place "tell the running daemon to also start polling project X" command.

Suggested Fix

Small to large:

  1. Make ao start register every project that has restorable sessions in last-stop.json (and/or every project in the global config — the right scope is a design call, but at minimum: union of cwd-project + every projectId appearing in last-stop.json.sessions[] and otherProjects[].sessions[]). The existing running.json.projects: [] is already an array — the wiring just needs to populate it from the right source on start.

  2. Make the restore loop cross-project-aware. Once registration is fixed, iterate the union of sessions[] and otherProjects[].sessions[], dispatching each session restore against its own project's session manager.

  3. Fix the log line. The "N session(s) from other projects were also stopped" message should (a) count the actual cross-project rows, not 1, and (b) list the other projects' sessions, not the cwd-project's. The wrong rendering is independently misleading even after the underlying restore is fixed.

  4. Add a dry-run / non-default behavior guard. If last-stop.json contains sessions whose projectId isn't being registered by this ao start, either (a) register them, or (b) print a loud warning enumerating exactly which sessions will be left terminated and ask for confirmation. Silent loss is the worst outcome.

Impact

  • ao stop && ao start <anything> is not a safe round-trip in any multi-project setup. Users who run this expecting "stop everything, start everything back" lose work silently. The flag matrix above shows this is independent of --restore.
  • The user's case here: 7 sessions across a different project went dark with no recourse other than per-session manual restore (which itself loses the lifecycle polling).
  • This makes the documented ao update flow (#1743's neighbor problem) doubly fragile — even after bug(cli): ao stop → ao update → ao start leaves sessions terminated, no restore prompt #1743 is fixed, the multi-project loss case still bites.
  • Restored sessions don't get lifecycle events anyway because the daemon only polls one project — restore is incomplete on a deeper axis.

Related

  • #1743 — adjacent restore-bug. ao update between stop and start destroys last-stop.json. Same family, different failure mode. Fixes likely live in nearby code.
  • #2110 — sidebar lists all terminated sessions. The 7 sessions this bug strands appear there indefinitely.
  • #1128 — RFC: multi-base-branch orchestrators (one orchestrator per branch). The single-project polling assumption that drives this bug also constrains that RFC.
  • #1522 (CLOSED) — "Dynamic project supervisor: auto-poll active globally-registered projects". Closed; whatever shipped clearly didn't cover the start-time registration path. Worth re-reading that fix's scope.
  • #1590 (CLOSED) — "kill+restore session starts fresh chat instead of resuming (multi-project)". Different bug but in the multi-project restore neighborhood.

Edited: Retitled and rewritten from the original "ao start --restore drops cross-project sessions" to reflect that the bug is scope-level (single-project registration), not flag-level. The flag matrix in §"Affected invocations" makes the broader effect explicit. Body updated end-to-end. — 2026-06-08

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions