From 8e46ebc792b240cc1db011c2a48a185d8ce0b1b8 Mon Sep 17 00:00:00 2001 From: Kallas95 Date: Fri, 14 Aug 2026 21:05:31 +0200 Subject: [PATCH 1/3] docs: pin that alt-screen harnesses expose no scrollback to Herdr Document that alternative-screen harnesses, including Claude Code, expose no scrollback history to Herdr, so any capture of such a pane is bounded to the visible window regardless of the requested --lines. Add a viewport-stability non-regression test to the real-herdr smoke suite verifying that no firstmate capture moves the pane viewport. --- docs/herdr-backend.md | 2 ++ tests/fm-backend-herdr-smoke.test.sh | 29 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/docs/herdr-backend.md b/docs/herdr-backend.md index 4c75fd8bc5..fce3cee031 100644 --- a/docs/herdr-backend.md +++ b/docs/herdr-backend.md @@ -226,6 +226,8 @@ The poll density bounds the residual possibility of an extremely fast complete t `pane read --lines N` can return empty output when N is below the viewport height. The capture owner requests at least 200 lines from Herdr and trims locally to the caller's bound. This generous floor is required for small composer and peek reads. +Alternative-screen harnesses, including Claude Code, expose no scrollback history to Herdr at all. +Any capture of such a pane is therefore bounded to the visible window regardless of the requested `--lines`. Herdr's native agent state can read idle while a harness waits on its own long foreground tool. The shared crew-state path therefore accepts a native `busy` as evidence of activity but never a native `idle` as evidence that a worker has stopped; the task's own semantic busy state (`bin/fm-busy-lib.sh`) decides that. diff --git a/tests/fm-backend-herdr-smoke.test.sh b/tests/fm-backend-herdr-smoke.test.sh index 98f1db2e97..919c5da1d0 100755 --- a/tests/fm-backend-herdr-smoke.test.sh +++ b/tests/fm-backend-herdr-smoke.test.sh @@ -285,6 +285,35 @@ case "$p" in esac pass "real herdr: current_path reads the pane's live cwd" +# --- viewport stability: a firstmate capture never moves the pane scroll ----- +# (fm-herdr-scroll-scout, section 8) Alternative-screen harnesses expose no +# scrollback history to Herdr, so every capture of an agent pane is bounded to +# the visible window. The one behavior of OUR code worth pinning is that a +# capture never MOVES the viewport: exactly like tmux capture-pane, `herdr +# pane read` must leave the pane's scroll untouched. This fails the moment a +# Herdr version or an adapter change introduces a capture that scrolls the +# view. Uses its own throwaway pane so the 500 lines of seeded history cannot +# interfere with the rest of the suite. +SCROLL_LABEL="fm-smoke-scroll" +SCROLL_IDS=$(fm_backend_herdr_create_task "$CONTAINER" "$SCROLL_LABEL" /tmp) || fail "could not create the viewport-stability scenario's tab" +read -r _SCROLL_TAB_ID SCROLL_PANE_ID </dev/null +fm_backend_herdr_capture_ansi "$SCROLL_TARGET" 20 >/dev/null +fm_backend_herdr_composer_state "$SCROLL_TARGET" >/dev/null +scroll_after=$(herdr pane get "$SCROLL_PANE_ID" --session "$SESSION" | jq -c '.result.pane.scroll') +[ "$scroll_before" = "$scroll_after" ] || fail "a firstmate capture moved the pane viewport: $scroll_before -> $scroll_after" +pass "real herdr: a firstmate capture never moves the pane viewport (no scroll for alternative-screen harnesses)" +fm_backend_herdr_kill "$SESSION:$SCROLL_PANE_ID" + # --- busy_state on a real claude harness (verified in herdr-verification-p2.md) --- if [ "${FM_HERDR_SMOKE_REAL_CLAUDE:-0}" = 1 ] && command -v claude >/dev/null 2>&1; then From 25ce88a243825edab6b2557d88af8095ffee890b Mon Sep 17 00:00:00 2001 From: Kallas95 Date: Fri, 14 Aug 2026 21:14:56 +0200 Subject: [PATCH 2/3] no-mistakes(review): guard scroll read and capture failures in viewport test --- tests/fm-backend-herdr-smoke.test.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/fm-backend-herdr-smoke.test.sh b/tests/fm-backend-herdr-smoke.test.sh index 919c5da1d0..15d15dfad9 100755 --- a/tests/fm-backend-herdr-smoke.test.sh +++ b/tests/fm-backend-herdr-smoke.test.sh @@ -306,9 +306,14 @@ SCROLL_TARGET="$SESSION:$SCROLL_PANE_ID" fm_backend_herdr_send_text_line "$SCROLL_TARGET" 'seq 1 500' || fail "could not seed scrollback for the viewport-stability check" sleep 0.5 scroll_before=$(herdr pane get "$SCROLL_PANE_ID" --session "$SESSION" | jq -c '.result.pane.scroll') -fm_backend_herdr_capture "$SCROLL_TARGET" 40 >/dev/null -fm_backend_herdr_capture_ansi "$SCROLL_TARGET" 20 >/dev/null -fm_backend_herdr_composer_state "$SCROLL_TARGET" >/dev/null +{ [ -n "$scroll_before" ] && [ "$scroll_before" != null ]; } \ + || fail "could not read .result.pane.scroll before the captures" +fm_backend_herdr_capture "$SCROLL_TARGET" 40 >/dev/null \ + || fail "capture failed during the viewport-stability check" +fm_backend_herdr_capture_ansi "$SCROLL_TARGET" 20 >/dev/null \ + || fail "capture_ansi failed during the viewport-stability check" +cs=$(fm_backend_herdr_composer_state "$SCROLL_TARGET") +[ "$cs" != unknown ] || fail "composer_state could not capture the pane during the viewport-stability check" scroll_after=$(herdr pane get "$SCROLL_PANE_ID" --session "$SESSION" | jq -c '.result.pane.scroll') [ "$scroll_before" = "$scroll_after" ] || fail "a firstmate capture moved the pane viewport: $scroll_before -> $scroll_after" pass "real herdr: a firstmate capture never moves the pane viewport (no scroll for alternative-screen harnesses)" From 535a8b037b43876920d38f0b202bbe6d806ec3ed Mon Sep 17 00:00:00 2001 From: Kallas95 Date: Fri, 14 Aug 2026 21:21:06 +0200 Subject: [PATCH 3/3] no-mistakes(review): drop machine-dependent composer_state assertion in viewport test --- tests/fm-backend-herdr-smoke.test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/fm-backend-herdr-smoke.test.sh b/tests/fm-backend-herdr-smoke.test.sh index 15d15dfad9..3b1c2f1e23 100755 --- a/tests/fm-backend-herdr-smoke.test.sh +++ b/tests/fm-backend-herdr-smoke.test.sh @@ -312,8 +312,7 @@ fm_backend_herdr_capture "$SCROLL_TARGET" 40 >/dev/null \ || fail "capture failed during the viewport-stability check" fm_backend_herdr_capture_ansi "$SCROLL_TARGET" 20 >/dev/null \ || fail "capture_ansi failed during the viewport-stability check" -cs=$(fm_backend_herdr_composer_state "$SCROLL_TARGET") -[ "$cs" != unknown ] || fail "composer_state could not capture the pane during the viewport-stability check" +fm_backend_herdr_composer_state "$SCROLL_TARGET" >/dev/null scroll_after=$(herdr pane get "$SCROLL_PANE_ID" --session "$SESSION" | jq -c '.result.pane.scroll') [ "$scroll_before" = "$scroll_after" ] || fail "a firstmate capture moved the pane viewport: $scroll_before -> $scroll_after" pass "real herdr: a firstmate capture never moves the pane viewport (no scroll for alternative-screen harnesses)"