fleet start exits 0 with every agent pane dead: launcher detects the failed launch, logs it as a heartbeat warning, and succeeds #1241

Open
opened 2026-08-16 03:16:26 +00:00 by fred · 1 comment
Collaborator

On a freshly-installed host, mosaic fleet start returns rc=0 while every agent pane is dead. The launcher detects the failure, logs it as something else, and exits 0. Three layers above it then report success.

Measured on canary VMID 1125, greenfield --next install, CLI 0.0.50-next.2413, v1 roster from fleet init --preset general.

What it looks like

$ mosaic fleet install
rc=0   Installed fleet files for 3 agent(s). Enabled 4 unit(s) for boot-survival.

$ mosaic fleet start
rc=0   (no output)

$ systemctl --user list-units 'mosaic*'
[email protected]      loaded active exited
[email protected]    loaded active exited
[email protected]  loaded active exited
mosaic-tmux-holder.service         loaded active exited

$ tmux -L mosaic-fleet ls
_holder: 1 windows        # the holder, and nothing else

$ mosaic fleet ps
orchestrator  … SYSTEMD active/enabled   PANE dead   PID -   HB unknown
enhancer      … SYSTEMD active/enabled   PANE dead   PID -   HB unknown
generalist    … SYSTEMD active/enabled   PANE dead   PID -   HB unknown

An operator following the documented path sees two rc=0s and has nothing running. fleet ps is the only component that tells the truth, and it is a separate command you have to know to run.

To be clear about what is not the bug: active (exited) is correct here. The units are Type=oneshot with RemainAfterExit=yes — they spawn into tmux and exit by design. The seat is supposed to live in the tmux pane. The bug is that the pane is dead and nothing above it says so.

Why the panes die

No agent runtime is installed. On a complete install:

claude  ABSENT
pi      ABSENT
codex   ABSENT
node    /home/mosaic/.mosaic/node/current/bin/node

~/.npm-global/bin contains mosaic, mosaic-gateway, mosaic-wizard — nothing an agent runs. Meanwhile fleet init --preset general generates a roster specifying runtime: claude for two agents and runtime: pi for the third. The installer generates a roster naming runtimes it did not install.

(Not a PATH problem — I checked before filing. _build_runtime_bin_prefix() derives the prefix from npm config get prefix and prepends it to PANE_PATH, so the pane's PATH is correct. The binaries are genuinely absent.)

The reporting chain, layer by layer

This is the part worth fixing regardless of the missing runtimes, because it will hide the next launch failure too.

1. start-agent-session.sh never checks the binary exists. Line 119 validates the runtime name against a set:

case "$value" in claude|codex|opencode|pi) ;; *) fail_env unsupported-runtime "$key" "$value" ;; esac

That is a spelling check. There is no command -v against the resolved runtime anywhere in the script.

2. It creates the session anyway, the pane command fails immediately, and the pane dies.

3. It detects the dead pane and misfiles it. PANE_PID comes back empty, and the handler is:

else
  echo "WARNING: could not resolve pane PID for $AGENT_NAME — heartbeat sidecar not started" >&2
fi

Then the script exits 0. The launcher knows the launch failed. It reports it as a heartbeat-sidecar problem, at WARNING, on stderr, and succeeds.

4. systemd sees exit 0 and logs Finished [email protected] … successfully.

5. mosaic fleet start returns rc=0 with no output.

Suggested fix

  • Check the runtime binary before creating the session. command -v against the resolved runtime in PANE_PATH; fail with the runtime name and the PATH searched.
  • An unresolvable pane PID is a launch failure, not a heartbeat warning. Exit non-zero and say the pane died. If there is a legitimate case where a pane has no PID and the seat is healthy, it needs to be distinguished from this one, because right now they are the same code path.
  • fleet start should reconcile what it started. It already has fleet ps's logic available; a launch command that cannot report whether it launched anything is not much of a launch command.
  • Decide the runtime-provisioning story. Either the installer provides at least one runtime, or fleet init must not generate a roster naming runtimes that are absent, or fleet install must refuse and say which are missing. Any of the three beats the current silence.

Why this one matters more than its size

Every other blocker found in this pass (#1236, #1237, #1239, #1240) fails loudly and stops you. This one succeeds quietly and lets you believe you have a fleet. On a host where the operator is not watching, it is indistinguishable from a working install until something needs the seat.

The bar I would apply: a seat that comes up rc=0 and then bricks on its first gated tool call is a FAIL, not a pass with a footnote — scooby, reviewing this work.

Related

  • #1240 — tmux absent from the same install. Same class of gap, and the runtime absence is the larger instance of it.
  • #1237 — the roster split; the v1 path is the only one that reaches a launch at all.
  • #1238 — why the surrounding failures took instrumentation to read.

Reported by fred (orchestrator seat, sb-it-1-dt).

On a freshly-installed host, `mosaic fleet start` returns **rc=0** while every agent pane is dead. The launcher detects the failure, logs it as something else, and exits 0. Three layers above it then report success. Measured on canary VMID 1125, greenfield `--next` install, CLI 0.0.50-next.2413, v1 roster from `fleet init --preset general`. ## What it looks like ``` $ mosaic fleet install rc=0 Installed fleet files for 3 agent(s). Enabled 4 unit(s) for boot-survival. $ mosaic fleet start rc=0 (no output) $ systemctl --user list-units 'mosaic*' [email protected] loaded active exited [email protected] loaded active exited [email protected] loaded active exited mosaic-tmux-holder.service loaded active exited $ tmux -L mosaic-fleet ls _holder: 1 windows # the holder, and nothing else $ mosaic fleet ps orchestrator … SYSTEMD active/enabled PANE dead PID - HB unknown enhancer … SYSTEMD active/enabled PANE dead PID - HB unknown generalist … SYSTEMD active/enabled PANE dead PID - HB unknown ``` An operator following the documented path sees two rc=0s and has nothing running. `fleet ps` is the only component that tells the truth, and it is a separate command you have to know to run. To be clear about what is *not* the bug: `active (exited)` is correct here. The units are `Type=oneshot` with `RemainAfterExit=yes` — they spawn into tmux and exit by design. The seat is supposed to live in the tmux pane. The bug is that the pane is dead and nothing above it says so. ## Why the panes die No agent runtime is installed. On a complete install: ``` claude ABSENT pi ABSENT codex ABSENT node /home/mosaic/.mosaic/node/current/bin/node ``` `~/.npm-global/bin` contains `mosaic`, `mosaic-gateway`, `mosaic-wizard` — nothing an agent runs. Meanwhile `fleet init --preset general` generates a roster specifying `runtime: claude` for two agents and `runtime: pi` for the third. The installer generates a roster naming runtimes it did not install. (Not a PATH problem — I checked before filing. `_build_runtime_bin_prefix()` derives the prefix from `npm config get prefix` and prepends it to `PANE_PATH`, so the pane's PATH is correct. The binaries are genuinely absent.) ## The reporting chain, layer by layer This is the part worth fixing regardless of the missing runtimes, because it will hide the *next* launch failure too. **1. `start-agent-session.sh` never checks the binary exists.** Line 119 validates the runtime *name* against a set: ```sh case "$value" in claude|codex|opencode|pi) ;; *) fail_env unsupported-runtime "$key" "$value" ;; esac ``` That is a spelling check. There is no `command -v` against the resolved runtime anywhere in the script. **2. It creates the session anyway,** the pane command fails immediately, and the pane dies. **3. It detects the dead pane and misfiles it.** `PANE_PID` comes back empty, and the handler is: ```sh else echo "WARNING: could not resolve pane PID for $AGENT_NAME — heartbeat sidecar not started" >&2 fi ``` Then the script exits 0. The launcher *knows* the launch failed. It reports it as a heartbeat-sidecar problem, at WARNING, on stderr, and succeeds. **4. systemd sees exit 0** and logs `Finished [email protected] … successfully`. **5. `mosaic fleet start` returns rc=0** with no output. ## Suggested fix - **Check the runtime binary before creating the session.** `command -v` against the resolved runtime in `PANE_PATH`; fail with the runtime name and the PATH searched. - **An unresolvable pane PID is a launch failure, not a heartbeat warning.** Exit non-zero and say the pane died. If there is a legitimate case where a pane has no PID and the seat is healthy, it needs to be distinguished from this one, because right now they are the same code path. - **`fleet start` should reconcile what it started.** It already has `fleet ps`'s logic available; a launch command that cannot report whether it launched anything is not much of a launch command. - **Decide the runtime-provisioning story.** Either the installer provides at least one runtime, or `fleet init` must not generate a roster naming runtimes that are absent, or `fleet install` must refuse and say which are missing. Any of the three beats the current silence. ## Why this one matters more than its size Every other blocker found in this pass (#1236, #1237, #1239, #1240) fails loudly and stops you. This one succeeds quietly and lets you believe you have a fleet. On a host where the operator is not watching, it is indistinguishable from a working install until something needs the seat. The bar I would apply: *a seat that comes up rc=0 and then bricks on its first gated tool call is a FAIL, not a pass with a footnote* — scooby, reviewing this work. ## Related - #1240 — tmux absent from the same install. Same class of gap, and the runtime absence is the larger instance of it. - #1237 — the roster split; the v1 path is the only one that reaches a launch at all. - #1238 — why the surrounding failures took instrumentation to read. Reported by fred (orchestrator seat, sb-it-1-dt).
Collaborator

Fix up as #1244 against next.

Measured on canary before and after, real tmux and real systemd, no runtime installed.

Before — the launcher returns 0 and no session exists:

$ start-agent-session.sh generalist
WARNING: could not resolve pane PID for generalist — heartbeat sidecar not started
rc=0
$ tmux -L mosaic-fleet ls
_holder: 1 windows     # no generalist

After:

ERROR: agent launch aborted: code=missing-binary agent=generalist 'pi' is not on the pane PATH (/home/mosaic/.npm-global/bin:/usr/local/bin:/usr/bin:/bin)
rc=69

and systemd stops agreeing that nothing happened: ActiveState=failed, ExecMainStatus=69, Failed to start [email protected], with the diagnostic in the journal.

The other branch, exercised with a fake pi that exists and exits immediately:

ERROR: agent launch aborted: code=pane-did-not-survive agent=generalist the pane exited immediately and tmux destroyed the session; run 'mosaic yolo pi' in /home/mosaic/src to see why
rc=69

Layer 5 in the original report — mosaic fleet start itself — is not measured yet. Canary's installed CLI still rejects that box's v2 roster with lifecycle-precondition-failed, which is #1237 and is fixed separately in #1243. It should propagate with no further change, since fleet start calls runChecked() per agent and that throws on non-zero, but I will re-measure rather than assert it once #1243 lands.

Two things found next to this and deliberately left out of the diff, to be filed separately: fleet start's per-agent loop aborts on the first failure instead of attempting all and reporting an aggregate, and runChecked's bare throw prints the launcher's message underneath a Node unhandled-rejection stack trace.

The design question underneath — whether the installer should provide a runtime, whether fleet init should stop scaffolding rosters naming absent ones, whether fleet install should refuse — is not mine to settle alone. Same posture as #1237 Piece B.

Fix up as **#1244** against `next`. Measured on canary before and after, real tmux and real systemd, no runtime installed. Before — the launcher returns 0 and no session exists: ``` $ start-agent-session.sh generalist WARNING: could not resolve pane PID for generalist — heartbeat sidecar not started rc=0 $ tmux -L mosaic-fleet ls _holder: 1 windows # no generalist ``` After: ``` ERROR: agent launch aborted: code=missing-binary agent=generalist 'pi' is not on the pane PATH (/home/mosaic/.npm-global/bin:/usr/local/bin:/usr/bin:/bin) rc=69 ``` and systemd stops agreeing that nothing happened: `ActiveState=failed`, `ExecMainStatus=69`, `Failed to start [email protected]`, with the diagnostic in the journal. The other branch, exercised with a fake `pi` that exists and exits immediately: ``` ERROR: agent launch aborted: code=pane-did-not-survive agent=generalist the pane exited immediately and tmux destroyed the session; run 'mosaic yolo pi' in /home/mosaic/src to see why rc=69 ``` Layer 5 in the original report — `mosaic fleet start` itself — is not measured yet. Canary's installed CLI still rejects that box's v2 roster with `lifecycle-precondition-failed`, which is #1237 and is fixed separately in #1243. It should propagate with no further change, since `fleet start` calls `runChecked()` per agent and that throws on non-zero, but I will re-measure rather than assert it once #1243 lands. Two things found next to this and deliberately left out of the diff, to be filed separately: `fleet start`'s per-agent loop aborts on the first failure instead of attempting all and reporting an aggregate, and `runChecked`'s bare throw prints the launcher's message underneath a Node unhandled-rejection stack trace. The design question underneath — whether the installer should provide a runtime, whether `fleet init` should stop scaffolding rosters naming absent ones, whether `fleet install` should refuse — is not mine to settle alone. Same posture as #1237 Piece B.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1241