Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/verification/runtime-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,7 @@ herdr pane process-info --pane w1:p1 --session "$LAB" | jq -c '.result.process_i
```

Before the fix `fm_backend_agent_state herdr` read that second state as `alive`, so `bin/fm-control.sh <id> relaunch` and `bin/fm-spawn.sh --relaunch` were refused for as long as the registration lived, which is hours.
The registration is still present after the wait, and Herdr's own `pane report-agent` leaves the same shape behind on any pane, which is what the lifecycle-control guard uses.
The registration is still present after the wait, and a `pane report-agent` registration whose agent process exits under a nested shell leaves the same shape behind on any pane, which is what the lifecycle-control guard uses; with no nested shell, Herdr releases that registration within about a second.

Two vendor facts the fix rests on, both read from the outputs above and from `fm_backend_herdr_pane_process_state`'s `pane process-info` parse:

Expand Down Expand Up @@ -1744,7 +1744,7 @@ ok - real herdr 0.9.0 + pi 0.85.1: a running registered pi classifies alive at p
ok - real herdr 0.9.0 + pi 0.85.1: the registration left behind by a quit pi reads stale-agent and recovers as dead
```

`tests/fm-control-herdr-smoke.test.sh` proves the same shape through the control plane with no harness launched (the two `stale` lines under "Agent lifecycle control" above): a registration over a real agent-named process reads `alive`, stopping that process makes the pane read `stale-agent` and recover as `dead` while `agent get` still reports the record, `exit` then reports `already-stopped`, and `--relaunch` reuses the same endpoint with the local copy intact.
`tests/fm-control-herdr-smoke.test.sh` proves the same shape through the control plane with no harness launched (the two `stale` lines under "Agent lifecycle control" above): a registration over a real agent-named process running under a nested shell reads `alive`, stopping that process makes the pane read `stale-agent` and recover as `dead` while `agent get` still reports the record, `exit` then reports `already-stopped`, and `--relaunch` reuses the same endpoint with the local copy intact.
`tests/fm-backend-herdr.test.sh` pins the logic portably with canned `process-info` bodies over real processes, driving the signals apart: the identical shell-only foreground reads `stale-agent` for a childless shell and `live` when an agent-named process is still a descendant of that shell, a `working`, `done`, or `blocked` record over a shell-only pane reads the same as `idle`, an unreadable process view reads `unknown` and refuses husk closing, a transient prompt helper beside the shell settles into `stale-agent` on the next shell-only sample while a foreground that never settles within the bound still reads `live`, and `busy_state` verifies a `working` record before reporting busy.
`tests/fm-crew-state.test.sh` pins the recovery classifier: a stale registration over a shell-only pane reports agent gone rather than alive or unreachable, and a stale `working` record never reports the pane working.
A stale-registration pane is never a husk: create, reclaim, presentation recovery, and session cleanup keep refusing it, and only recovery reuses it.
Expand Down
31 changes: 31 additions & 0 deletions tests/fm-control-herdr-smoke.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@ $TASK_IDS
EOF
[ -n "$TAB_ID" ] && [ -n "$PANE_ID" ] || fail "create_task did not return tab/pane ids"

# Herdr starts the pane in the developer's login shell, whose prompt is not this
# test's to choose. A prompt drawn with an agent composer glyph - a `❯` prompt
# is exactly claude's bare composer row - anywhere in the viewport makes the
# final exit case read the composer as holding pending text instead of as
# unproven. An rc-free bash with a shell-glyph prompt on a cleared screen draws
# no recognized composer chrome, so every composer verdict below depends on this
# test alone. `exec` keeps the pane's shell pid.
BASH_BIN=$(command -v bash) || fail "bash not found"
printf -v BASH_Q '%q' "$BASH_BIN"
fm_backend_herdr_send_text_line "$SESSION:$PANE_ID" "exec env PS1='hsmoke\$ ' HISTFILE=/dev/null $BASH_Q --noprofile --norc" \
|| fail "could not pin a neutral shell prompt in the task pane"
fm_backend_herdr_send_text_line "$SESSION:$PANE_ID" "clear" \
|| fail "could not clear the login shell's output from the task pane"
neutral_screen_shown() { # the viewport holds nothing but the neutral prompt
fm_backend_herdr_visible_capture "$SESSION:$PANE_ID" 2>/dev/null \
| awk 'NF { n++; last = $0 } END { exit(n == 1 && last ~ /^hsmoke\$ *$/ ? 0 : 1) }'
}
for _ in $(seq 1 50); do
! neutral_screen_shown || break
sleep 0.1
done
neutral_screen_shown || fail "the task pane did not settle on a cleared neutral shell prompt"

{
echo "window=$SESSION:$PANE_ID"
echo "endpoint_task_id=hsmoke"
Expand Down Expand Up @@ -233,6 +256,14 @@ start_agent_process() {
|| version_fail "a real agent-named foreground process reads '$(fm_backend_herdr_pane_process_state "$SESSION" "$PANE_ID")' rather than 'agent' through pane process-info"
}

# Herdr keeps a registration after its agent exits only when a nested
# interactive shell sits under the pane's top shell (issue #4115); an agent that
# exits straight back to the top shell is released within about a second, which
# would race the stale-registration case below. So the agent runs under a
# nested shell, the crew shape `treehouse get` leaves behind. It inherits the
# neutral PS1 and HISTFILE from the top shell's environment.
fm_backend_herdr_send_text_line "$SESSION:$PANE_ID" "$BASH_Q --noprofile --norc" \
|| fail "could not start a nested shell in the task pane"
start_agent_process
herdr pane report-agent "$PANE_ID" --source fm-control-smoke --agent fm-control-smoke-agent \
--state idle --session "$SESSION" >/dev/null 2>&1 \
Expand Down
Loading