feat(fleet): launcher heartbeat sidecar — HB for all runtimes (pi/claude/codex) #584

Merged
jason.woltje merged 2 commits from feat/fleet-heartbeat-sidecar into main 2026-06-21 21:14:20 +00:00
Owner

Runtime-agnostic heartbeat: start-agent-session.sh resolves the pane PID and spawns a detached setsid sidecar that writes ~/.config/mosaic/fleet/run/.hb (ts/pid/status) every interval while the runtime PID is alive, self-terminating on death. Closes the HB=unknown gap for real pi/claude/codex sessions (previously only the dogfood stub heartbeat). Preserves PATH-baking + exec pane identity. Gate + 7 sh tests pass (incl. real-tmux .hb write).

Runtime-agnostic heartbeat: start-agent-session.sh resolves the pane PID and spawns a detached setsid sidecar that writes ~/.config/mosaic/fleet/run/<agent>.hb (ts/pid/status) every interval while the runtime PID is alive, self-terminating on death. Closes the HB=unknown gap for real pi/claude/codex sessions (previously only the dogfood stub heartbeat). Preserves PATH-baking + exec pane identity. Gate + 7 sh tests pass (incl. real-tmux .hb write).
jason.woltje added 1 commit 2026-06-21 20:53:18 +00:00
feat(fleet): launcher heartbeat sidecar — HB for all runtimes (pi/claude/codex)
Some checks failed
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
b50a062021
Replace the terminal `exec tmux` with a plain `tmux new-session -d` so the
launcher continues running after creating the pane. The script then resolves
the pane PID via `tmux list-panes -F '#{pane_pid}'` (with a brief retry loop)
and spawns a detached, runtime-agnostic heartbeat sidecar via `setsid bash -c
... &` + `disown`. The sidecar loops while `kill -0 <pane_pid>` succeeds,
writing ~/.config/mosaic/fleet/run/<AGENT>.hb atomically (tmp + mv) every
MOSAIC_HEARTBEAT_INTERVAL seconds (default 15), then exits naturally when the
runtime process dies — making `mosaic fleet ps` show stale then dead.
HB_RUN_DIR and interval are configurable via env; sidecar startup is
best-effort (failures warn but do not abort the launch). Two new shell tests
cover pane-PID resolution (test 6, real tmux) and sidecar invocation
correctness (test 7, fake-tmux + fake-setsid shims).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
jason.woltje added 1 commit 2026-06-21 20:59:00 +00:00
test(fleet): hermetic heartbeat tests — temp run-dir + no leaked sidecars
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
6dbd3d691c
Tests 3, 4, 5 previously returned synthetic pane PIDs (99999/99998/99997)
from their fake list-panes shims but did not set MOSAIC_HEARTBEAT_RUN_DIR,
causing the launcher to fall back to the real ~/.config/mosaic/fleet/run
and potentially spawn a background sidecar against an arbitrary host PID.

Fix:
- list-panes in tests 3/4/5 now returns empty string → PANE_PID stays
  unset → no sidecar is spawned for tests where heartbeat is not under test.
- MOSAIC_HEARTBEAT_RUN_DIR is exported to a per-test mktemp dir in each
  fake-tmux test (3, 4, 5) as defence-in-depth so even if the sidecar
  code path changes, it can never write to the real fleet run dir.
- New temp dirs are registered in CLEANUP_DIRS so they are removed by the
  existing EXIT trap.
- Tests 6 and 7 (the dedicated heartbeat tests) are unchanged: test 6 uses
  a real tmux pane PID + its own HB_RUN_DIR, test 7 intercepts via a fake
  setsid shim that captures args and exits immediately.
- All 7 tests pass; verify-sanitized.sh passes; no stray sidecar processes
  or unexpected .hb files are written to ~/.config/mosaic/fleet/run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
jason.woltje merged commit 7ced5588c9 into main 2026-06-21 21:14:20 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#584