From 9f6fd2ddee778ee666388c843cd547ea37d9e549 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 18:51:15 +0000 Subject: [PATCH] lane and lane-handoff refuse empty and flag-shaped flag values, and lane-handoff renames the window only once the handoff can no longer be refused (#80 slices B and C) Copilot's review of workBenches#91 found these against the vendored bytes, and each was still true at f87fd5a: - `lane --dir --all` took `--all` as the checkout (item 1), and `--force --dry-run` ran a real takeover whose why was `--dry-run`. The three spaced value arms now refuse a `-`-led value with 64 and name the `=` spelling. - `lane-handoff --lane=` and `--writers-file=` (items 6 and 8), and the other five `=` forms, took an empty value as no flag at all. One arm ahead of the seven now refuses it. Under them, the spaced form's refusal ran inside `$( )`, so it printed REFUSED and carried on: `--lane --restart` handed off the window's lane with exit 0. Every spaced arm now ends `|| exit`. - `have_tmux` answered for any tmux server, inside a client or not (item 10). It now requires `$TMUX`, as `lane`'s `tmux_here` does. - The window ref and id checks were prefix globs (item 3), so `s:1junk` and `@21junk` went into the append-only `window` sub-field. They are now whole-string matches, for tmux's answers and the launcher's fallbacks. - The window rename ran before the `--late` preconditions, the snapshot file, the row read and the `--restart` binding, and its failure was swallowed (items 2 and 11). It now runs after the last refusal and before the PAUSED record, and a refused rename is a refusal that names what had already landed. The suite's fake tmux gains `$FAKE_TMUX_RENAME_FAIL`. Slice A, the two fixes in skills/handoff/SKILL.md (items 4 and 7), is deferred. Co-Authored-By: Claude Opus 5.5 --- lane | 24 ++++++- lane-handoff | 125 ++++++++++++++++++++++++++------ tests/test_lane_helpers.sh | 141 +++++++++++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+), 25 deletions(-) diff --git a/lane b/lane index 5105fc1c..042c2265 100755 --- a/lane +++ b/lane @@ -225,6 +225,24 @@ take_arg() { *) die "$prog takes a lane and an optional profile: $prog [ []]" 64 ;; esac } +# A FLAG WHERE A VALUE BELONGS IS REFUSED, in the spaced spelling (opensoft/ +# openRepoTools#80 item 1, Copilot's review of workBenches#91 at `lane:215`). +# `lane --dir --all` took `--all` as the checkout: the flag a person typed was +# swallowed as a value and never took effect, so the run asked a different +# question from the one asked of it. `--force --dry-run` was worse, a REAL +# takeover whose why was the word `--dry-run`, written into another place's +# append-only log. `park`, `resume` and `status` refuse this with their shared +# `flag_value` (92eab6c) and `lane-handoff` with its own; this is the same +# answer for the three value-taking arms here. The `=` spelling is the way back +# for a value that really does begin with `-` — `--dir=--odd` takes whatever +# follows the `=` — and an empty one there is already refused at its arm. +not_a_flag() { # + case "$2" in + -*) die "$1 needs a value and '$2' is a flag: nothing here swallows the next word + whatever it is. A value that really does begin with \`-\` is given after an + \`=\`: $1=$2" 64 ;; + esac +} while [ $# -gt 0 ]; do if [ "$endopts" = 1 ]; then take_arg "$1"; shift; continue; fi case "$1" in @@ -233,11 +251,11 @@ while [ $# -gt 0 ]; do --switch) switch=1; shift ;; --dry-run) dry_run=1; shift ;; --request-handoff) request_flag=1; shift ;; - --force) force_why="${2-}"; [ -n "$force_why" ] || die "--force takes the WHY you are taking a lane another place is bound to: it is written into that lane's append-only log and it is the sentence the bound session reads when it refuses every prompt from then on (Amendment 18(e))" 64; shift 2 ;; + --force) force_why="${2-}"; [ -n "$force_why" ] || die "--force takes the WHY you are taking a lane another place is bound to: it is written into that lane's append-only log and it is the sentence the bound session reads when it refuses every prompt from then on (Amendment 18(e))" 64; not_a_flag --force "$force_why"; shift 2 ;; --force=*) force_why="${1#--force=}"; [ -n "$force_why" ] || die "--force takes the WHY you are taking a lane another place is bound to (Amendment 18(e))" 64; shift ;; - --wait) handoff_wait="${2-}"; [ -n "$handoff_wait" ] || die "--wait needs a number of seconds" 64; shift 2 ;; + --wait) handoff_wait="${2-}"; [ -n "$handoff_wait" ] || die "--wait needs a number of seconds" 64; not_a_flag --wait "$handoff_wait"; shift 2 ;; --wait=*) handoff_wait="${1#--wait=}"; [ -n "$handoff_wait" ] || die "--wait needs a number of seconds" 64; shift ;; - --dir) dir_flag="${2-}"; [ -n "$dir_flag" ] || die "--dir needs a path" 64; shift 2 ;; + --dir) dir_flag="${2-}"; [ -n "$dir_flag" ] || die "--dir needs a path" 64; not_a_flag --dir "$dir_flag"; shift 2 ;; --dir=*) dir_flag="${1#--dir=}"; [ -n "$dir_flag" ] || die "--dir needs a path" 64; shift ;; --) endopts=1; shift ;; -*) die "unknown option '$1' — see $prog --help" 64 ;; diff --git a/lane-handoff b/lane-handoff index e854cb7a..583980f6 100755 --- a/lane-handoff +++ b/lane-handoff @@ -102,13 +102,17 @@ # # 0 the handoff was written (and the pane respawned or exited, where asked) # 2 REFUSED — nothing was written, or the record was written and the act -# that follows it was refused before touching the pane +# that follows it was refused before touching the pane, or tmux would not +# rename the window and the record was NOT written (the refusal names +# what had landed before it: the handoff's top block, SWAPPING) # 1 a read failed, which is never "nothing to report" (Amendment 7(d)) # # $LANES_EDIT the register's reader/writer (default: beside this file) # $LANES_AGENT this agent's name for the record (default: claude) # $PCLAUDE the launcher (default: lclaude, else pclaude, else claude-profile) # $LANE_HANDOFF_NO_TMUX=1 never touch tmux (the identity fix, --exit, --restart) +# — and with `$TMUX` unset, this is not inside tmux, +# so tmux is not touched either # # lane-collision-protocol Amendment 17 (in force 2026-09-14T09:45:33Z), # Amendment 18(d) and Addendum 2 (in force 2026-09-14T16:50:32Z). @@ -241,6 +245,13 @@ why="" # `resume` and `status` (`92eab6c`): `--lane --restart` must not hand off a lane # called `--restart`, and `--flag=value` is the spelling that gives a `-`-led # value back. One reader, byte-identical to theirs. +# AND ITS REFUSAL ENDS THIS COMMAND, NOT ONLY ITS OWN SUBSHELL (opensoft/ +# openRepoTools#80). Every arm reads it as `x="$(flag_value …)"`, and a `die` +# inside `$( )` exits the subshell it runs in: the REFUSED line was printed and +# the run then went on with the value empty — `--lane --restart` handed off +# whatever lane the window resolved to, and `--writers-file ""` wrote a handoff +# with no briefs. So every arm ends `|| exit`, which carries the refusal's own +# status out, and this file sets no `-e` that would have done it silently. flag_value() { # case "${2-}" in '') die "$1 needs a value" 2 ;; @@ -273,19 +284,33 @@ while [ $# -gt 0 ]; do # record. --supervise) do_supervise=1; shift ;; --restart-status) do_status=1; shift ;; - --operation) operation="$(flag_value --operation "${2-}")"; shift 2 ;; + # AN `=` WITH NOTHING AFTER IT IS A VALUE THAT WAS TYPED AND LOST, never one + # that was not typed (opensoft/openRepoTools#80, Copilot's review of + # workBenches#91 at `lane-handoff:242` and `:248`). Each `--flag=*` arm below + # takes whatever follows the `=`, and every one of these values is read + # further down as "was this given at all?": `--lane=` fell through to the + # window's own lane and handed THAT one off, `--writers-file=` wrote the + # WRITERS section with no brief for a writer the caller had briefed, and + # `--dir=` took the record's directory in place of the one being named. The + # spaced spelling has always refused an empty value through `flag_value`; + # this is the same refusal for the other spelling, in ONE arm ahead of the + # seven, because `case` takes the first pattern that matches. The arms below + # keep taking a value that begins with `-`, which is what `=` is for. + --operation= | --at= | --lane= | --agent= | --transcript= | --writers-file= | --dir=) + die "${1%=} needs a value, and '$1' gives it an empty one. Give it one after the '=', or leave the flag out." 2 ;; + --operation) operation="$(flag_value --operation "${2-}")" || exit; shift 2 ;; --operation=*) operation="${1#--operation=}"; shift ;; - --at) late_at="$(flag_value --at "${2-}")"; shift 2 ;; + --at) late_at="$(flag_value --at "${2-}")" || exit; shift 2 ;; --at=*) late_at="${1#--at=}"; shift ;; - --lane) lane="$(flag_value --lane "${2-}")"; shift 2 ;; + --lane) lane="$(flag_value --lane "${2-}")" || exit; shift 2 ;; --lane=*) lane="${1#--lane=}"; shift ;; - --agent) agent="$(flag_value --agent "${2-}")"; shift 2 ;; + --agent) agent="$(flag_value --agent "${2-}")" || exit; shift 2 ;; --agent=*) agent="${1#--agent=}"; shift ;; - --transcript) transcript="$(flag_value --transcript "${2-}")"; shift 2 ;; + --transcript) transcript="$(flag_value --transcript "${2-}")" || exit; shift 2 ;; --transcript=*) transcript="${1#--transcript=}"; shift ;; - --writers-file) writers_file="$(flag_value --writers-file "${2-}")"; shift 2 ;; + --writers-file) writers_file="$(flag_value --writers-file "${2-}")" || exit; shift 2 ;; --writers-file=*) writers_file="${1#--writers-file=}"; shift ;; - --dir) dir_flag="$(flag_value --dir "${2-}")"; shift 2 ;; + --dir) dir_flag="$(flag_value --dir "${2-}")" || exit; shift 2 ;; --dir=*) dir_flag="${1#--dir=}"; shift ;; --) shift; while [ $# -gt 0 ]; do why="${why:+$why }$1"; shift; done ;; -*) die "unknown option '$1' — see $prog --help" 2 ;; @@ -406,9 +431,17 @@ fi utc_now() { date -u +%Y-%m-%dT%H:%M:%SZ; } UTC="$(utc_now)" +# TMUX IS THIS LANE'S ONLY FROM INSIDE IT (opensoft/openRepoTools#80 item 10, +# Copilot's review of workBenches#91 at `lane-handoff:351`). A `tmux` binary on +# PATH is not a tmux client: from an ordinary shell on a host where any tmux +# server runs — another person's, a leftover one — every `display-message` +# below answers about that server's most recent client, and the rename, the +# record's `window` and `--restart`/`--exit` would then read or act on a window +# that is not this lane's. `$TMUX` is set only inside a real client, which is +# the test `lane`'s `tmux_here` makes and the one made here. have_tmux() { [ "${LANE_HANDOFF_NO_TMUX:-0}" = 1 ] && return 1 - command -v tmux >/dev/null 2>&1 + [ -n "${TMUX:-}" ] && command -v tmux >/dev/null 2>&1 } # THIS COMMAND'S OWN ABSOLUTE PATH, because tmux starts the supervisor and a @@ -1549,19 +1582,37 @@ fi # the HARNESS's own `tmux` field — which `window-session` matches a session # record on, and which is where the PANE comes from — is `:<@id>`. # Reading one for the other is how a record names a window nothing can resolve. +# AND EACH IS CHECKED AS A WHOLE STRING (opensoft/openRepoTools#80 item 3, +# Copilot's review of workBenches#91 at `lane-handoff:434`). `*:[0-9]*` and +# `@[0-9]*` were prefix globs, so `session:1junk` and `@123junk` passed — +# from tmux and from the launcher's environment alike — and went into the +# append-only `window` sub-field, where `window-lane` can never resolve them. +# The launcher and `lane-start` hold the same two values to `^.+:[0-9]+$` and +# `^@[0-9]+$`; these are those two rules, spelled in `case` as `lane-start` +# spells its own id check. The ref: a session part that is not empty, then +# digits and nothing else after the LAST `:`. +window_ref_ok() { # : + case "${1##*:}" in '' | *[!0-9]*) return 1 ;; esac + case "$1" in ?*:*) return 0 ;; esac + return 1 +} +window_id_ok() { # @ + case "$1" in @ | @*[!0-9]*) return 1 ;; @*) return 0 ;; esac + return 1 +} win=""; wid=""; window_name=""; win_harness="" if have_tmux; then win="$(tmux display-message -p '#S:#I' 2>/dev/null || :)" - case "$win" in *:[0-9]*) : ;; *) win="" ;; esac + window_ref_ok "$win" || win="" wid="$(tmux display-message -p '#{window_id}' 2>/dev/null || :)" - case "$wid" in @[0-9]*) : ;; *) wid="" ;; esac + window_id_ok "$wid" || wid="" window_name="$(tmux display-message -p '#W' 2>/dev/null || :)" win_harness="$(tmux display-message -p '#{session_name}:#{window_id}' 2>/dev/null || :)" fi [ -n "$win" ] || win="${WORKBENCHES_CLAUDE_WINDOW_REF:-}" -case "$win" in *:[0-9]*) : ;; *) win="" ;; esac +window_ref_ok "$win" || win="" [ -n "$wid" ] || wid="${WORKBENCHES_CLAUDE_WINDOW_ID:-}" -case "$wid" in @[0-9]*) : ;; *) wid="" ;; esac +window_id_ok "$wid" || wid="" window_ref="${win}${wid:+ $wid}" if [ -z "$lane" ] && [ -n "$window_name" ]; then @@ -1814,15 +1865,10 @@ if [ "$do_restart" = 1 ]; then done fi -# THE OTHER TWO NAMES MADE TO MATCH IT, mechanically. The window is renamed -# here; the SESSION's name is the lane's messaging address (Amendment 2) and -# there is no API to rename a running session from outside it, so where the -# live record's name is not the lane the act is PRINTED — it is the operator's -# `/rename`, and it is not a question. -if have_tmux && [ -n "$window_name" ] && [ "$window_name" != "$lane" ]; then - tmux rename-window "$lane" 2>/dev/null && - step "window renamed '$window_name' → '$lane' (Amendment 8(b): the window IS the lane)" -fi +# THE WINDOW IS NOT RENAMED HERE ANY MORE: the refusals between here and the +# record must leave it as they found it, two of them say so in as many words, +# and now they do. The rename is section 3a below, after the last of them +# (opensoft/openRepoTools#80). # ---------------------------- the LATE record's preconditions, read FIRST # @@ -2145,6 +2191,41 @@ if [ "$handoff_written" = 1 ] && [ "${LANES_NO_GIT:-0}" != 1 ]; then fi fi +# ------------------------- 3a. the window takes the lane's name, and only now +# +# THE OTHER TWO NAMES MADE TO MATCH IT, mechanically. The window is renamed +# here; the SESSION's name is the lane's messaging address (Amendment 2) and +# there is no API to rename a running session from outside it, so where the +# live record's name is not the lane the act is PRINTED, beside the read of +# that record above — it is the operator's `/rename`, and it is not a question. +# +# AFTER THE LAST REFUSAL AND BEFORE THE FIRST RECORD (opensoft/openRepoTools#80 +# items 2 and 11, Copilot's review of workBenches#91 at `lane-handoff:519`). The +# rename used to run before the `--late` preconditions, the writers' snapshot +# file, the row read and the `--restart` binding — every one of them a refusal, +# two of them saying that nothing was renamed — so a refused handoff left the +# window carrying the name of a lane it had not handed off. And its failure was +# swallowed: the `&&` gated only the message, and the PAUSED record, the row and +# the restart line went on as if the window were the lane's, which is how the +# next handoff from that window binds the wrong lane. `lane-start` holds this +# same rename fatal, and so does this command now: a rename tmux refuses is a +# refusal, made before the PAUSED record, the LANES.md line and the row, and +# before any pane is touched. What HAS landed by then is said, because the act +# that follows is a re-run and a person deciding on one needs to know. +if have_tmux && [ -n "$window_name" ] && [ "$window_name" != "$lane" ]; then + rn_err="" + if ! rn_err="$(tmux rename-window "$lane" 2>&1 >/dev/null)"; then + rn_landed="" + [ "$handoff_written" = 1 ] && + rn_landed=" The handoff's new top block IS written, and a re-run puts a newer one above it." + [ -n "$lc_on" ] && + rn_landed="$rn_landed The lane reads SWAPPING until a handoff finishes, and the next one supersedes this one." + die "tmux would not rename this window '$window_name' → '$lane'${rn_err:+ (tmux: $rn_err)}, and the window IS the lane (Amendment 8(b)): a record written beside a window that still carries another name is how the next handoff from it binds the wrong lane. So the PAUSED record, the LANES.md line and the row were NOT written, and no pane was touched.$rn_landed Rename the window, then run the same command again: + tmux rename-window $lane" 2 + fi + step "window renamed '$window_name' → '$lane' (Amendment 8(b): the window IS the lane)" +fi + # ------------------------------------------------------- 4. the swap record # # THE FREE TEXT IS SANITIZED ONCE: Amendment 7(b)/R26 refuses any payload or diff --git a/tests/test_lane_helpers.sh b/tests/test_lane_helpers.sh index 53e5d3c1..9c118a33 100755 --- a/tests/test_lane_helpers.sh +++ b/tests/test_lane_helpers.sh @@ -297,6 +297,12 @@ cat > "$SANDBOX/fakebin/tmux" <<'FAKE' # # AND `send-keys` IS LOGGED, exactly as `rename-window` always was: the lock and # the offer's `no` are acts on a pane and this is how a case sees them. +# +# AND A RENAME CAN BE REFUSED (opensoft/openRepoTools#80 item 11). Under +# `$FAKE_TMUX_RENAME_FAIL=1` the `rename-window` is still LOGGED — a case reads +# that it was attempted — and then refused the way tmux refuses one, on stderr +# with a nonzero exit, and the name does not stick. Unset, nothing changes for +# any case written before it. case "${1-}" in list-windows) if [ -n "${FAKE_TMUX_WINDOWS:-}" ]; then @@ -353,6 +359,10 @@ case "${1-}" in esac ;; rename-window) printf 'rename-window %s\n' "${2-}" >> "${FAKE_TMUX_LOG:-/dev/null}" + if [ "${FAKE_TMUX_RENAME_FAIL:-0}" = 1 ]; then + printf 'no current client\n' >&2 + exit 1 + fi if [ "${FAKE_TMUX_RENAME_STICKS:-0}" = 1 ] && [ -n "${FAKE_TMUX_NAME_FILE:-}" ]; then printf '%s\n' "${2-}" > "$FAKE_TMUX_NAME_FILE" fi ;; @@ -5927,6 +5937,24 @@ has "…naming it as one" "$err" "unknown option '-repoA11-1'" run env -u TMUX "$LANE" -- "-repoA11-1" "$FAKE_PCLAUDE_LOG" +run env -u TMUX "$LANE" --dir --all /dev/null | cksum; } +hfv_before="$(hfv_fp)" +for hfv_f in --operation= --at= --lane= --agent= --transcript= --writers-file= --dir=; do + run env PATH="$A17PATH" FAKE_TMUX_WINDOW="hfsess:@21" CLAUDE_CODE_SESSION_ID="$HF_ID" \ + CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-4 "$hfv_f" clear + is "lane-handoff $hfv_f, with nothing after the =, is refused" "$rc" 2 + has "…as a flag given an empty value" "$err" "${hfv_f%=} needs a value, and '$hfv_f' gives it an empty one" +done +run env PATH="$A17PATH" FAKE_TMUX_WINDOW="hfsess:@21" CLAUDE_CODE_SESSION_ID="$HF_ID" \ + CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane --restart clear +is "lane-handoff --lane --restart is refused" "$rc" 2 +has "…as a flag where the lane belongs" "$err" "--lane needs a value and '--restart' is a flag" +is "…and it STOPS there: one refusal, nothing after it" \ + "$(printf '%s\n' "$err" | command grep -c 'REFUSED')" 1 +hasnt "…so no lane was resolved from the window in its place" "$err" "lane-handoff: lane=" +run env PATH="$A17PATH" FAKE_TMUX_WINDOW="hfsess:@21" CLAUDE_CODE_SESSION_ID="$HF_ID" \ + CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-4 --writers-file "" clear +is "…and so is a spaced --writers-file given the empty string" "$rc" 2 +has "…in flag_value's own words" "$err" "--writers-file needs a value" +hasnt "…which no longer run on into the poll" "$err" "writers polled" +is "none of those refusals wrote a byte: the logs, the register, its commit" "$(hfv_fp)" "$hfv_before" +# AND THE `=` SPELLING STILL TAKES A VALUE THAT BEGINS WITH `-`, which is what +# it is for: such a value reaches the validation it would always have met. +run env PATH="$A17PATH" CLAUDE_CODE_SESSION_ID="$HF_ID" "$HANDOFF_CMD" --lane=-repoHF-4 clear +is "--lane= is still taken, and refused by the lane-name fence" "$rc" 2 +has "…as the lane name it is not" "$err" "'-repoHF-4' is not a lane name" +hasnt "…never as a value that was not given" "$err" "needs a value" + +# ------------------ 3c. the window: renamed only once nothing can refuse +# +# opensoft/openRepoTools#80 items 2, 3, 10 and 11 (Copilot's review of +# workBenches#91 at `lane-handoff:519`, `:434` and `:351`). Four lanes of their +# own, so that each case reads one record. +for hfw_l in repoHF-80 repoHF-81 repoHF-82 repoHF-83; do + hf_seed_handoff "$WIP/handoffs/repoHF/$hfw_l.md" "$hfw_l" +done +git -C "$WIP" add -- handoffs/repoHF >/dev/null 2>&1 +git -C "$WIP" commit -q -m "seed the #80 window cases' handoffs" +git -C "$WIP" pull -q --rebase origin main 2>/dev/null || : +git -C "$WIP" push -q origin main +for hfw_l in repoHF-80 repoHF-81 repoHF-82 repoHF-83; do + hf_row "$hfw_l" "harness \`$HF_ID\`" + hf_seed_log "$hfw_l" "2026-09-14T09:00:00Z" +done + +# A REFUSED HANDOFF RENAMES NOTHING (item 2). repoHF-7's relaunch is recorded +# above, so a late record on it is refused — by a gate that ran AFTER the rename +# until this fix, and in a window named `claude` (the fake's `#W`). +hfw_before="$(command grep -c 'rename-window repoHF-7' "$FAKE_TMUX_LOG" || :)" +run env PATH="$A17PATH" FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="hfsess:@21" CLAUDE_CODE_SESSION_ID="$HF4_ID" \ + CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-7 --late --at "2026-09-14T12:02:27Z" +is "a refused --late is still refused" "$rc" 2 +is "…and leaves the window as it found it, never named for a lane it did not hand off" \ + "$(command grep -c 'rename-window repoHF-7' "$FAKE_TMUX_LOG" || :)" "$hfw_before" + +# A RENAME TMUX REFUSES IS A REFUSAL, BEFORE THE RECORD (item 11). It was +# swallowed: the PAUSED record and the row went on as if the window were the +# lane's. +run env PATH="$A17PATH" FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="hfsess:@21" FAKE_TMUX_RENAME_FAIL=1 \ + CLAUDE_CODE_SESSION_ID="$HF_ID" CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-80 clear +is "a rename tmux refuses is a refusal" "$rc" 2 +has "…naming the rename, with tmux's own words" "$err" "tmux would not rename this window 'claude' → 'repoHF-80' (tmux: no current client)" +has "…and the act that clears it" "$err" "tmux rename-window repoHF-80" +is "…having tried the rename once" "$(command grep -c 'rename-window repoHF-80$' "$FAKE_TMUX_LOG" || :)" 1 +is "…and written no PAUSED record beside it" "$(command grep -c '^PAUSED' "$LOGD/repoHF-80.md")" 0 +hasnt "…nor flipped the row" "$(grep '^| `repoHF-80`' "$LANES")" "| PAUSED " +has "…while saying what HAD landed: the handoff's new top block" "$err" "new top block IS written" +run env PATH="$A17PATH" FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="hfsess:@21" CLAUDE_CODE_SESSION_ID="$HF_ID" \ + CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-80 clear +is "…and the same command, once tmux will rename, is the whole act" "$rc" 0 +has "…renaming the window" "$err" "window renamed 'claude' → 'repoHF-80'" +is "…writing the one PAUSED record" "$(command grep -c '^PAUSED' "$LOGD/repoHF-80.md")" 1 +has "…and flipping the row" "$(grep '^| `repoHF-80`' "$LANES")" "| PAUSED " + +# TMUX IS TOUCHED ONLY FROM INSIDE IT (item 10). The suite exports `$TMUX` for +# every case; here it is unset, the shape of an ordinary shell on a host where +# some tmux server runs, and the fake on PATH would answer for that server. +hfw_before="$(command grep -c 'rename-window repoHF-81' "$FAKE_TMUX_LOG" || :)" +run env -u TMUX -u WORKBENCHES_CLAUDE_WINDOW_REF -u WORKBENCHES_CLAUDE_WINDOW_ID \ + PATH="$A17PATH" FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="hfsess:@21" CLAUDE_CODE_SESSION_ID="$HF_ID" \ + CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-81 clear +is "a handoff from a shell outside tmux exits 0" "$rc" 0 +is "…renaming no window: a tmux on PATH is not this shell's client" \ + "$(command grep -c 'rename-window repoHF-81' "$FAKE_TMUX_LOG" || :)" "$hfw_before" +hasnt "…and recording no window it read from somebody else's" "$(command grep '^PAUSED' "$LOGD/repoHF-81.md")" "; window " +has "…while the record itself is written" "$(cat "$LOGD/repoHF-81.md")" "PAUSED — lane repoHF-81" + +# THE WINDOW REFS ARE WHOLE STRINGS (item 3): `:` and +# `@`, from tmux and from the launcher's environment alike. +run env PATH="$A17PATH" FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="hfsess:@21junk" FAKE_TMUX_WINDOW_INDEX="1junk" \ + WORKBENCHES_CLAUDE_WINDOW_REF="hfsess:7" WORKBENCHES_CLAUDE_WINDOW_ID="@77" \ + CLAUDE_CODE_SESSION_ID="$HF_ID" CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-82 clear +is "a handoff whose tmux answers malformed refs exits 0" "$rc" 0 +hfw_log="$(command grep '^PAUSED' "$LOGD/repoHF-82.md")" +hasnt "…recording no index with a tail" "$hfw_log" "1junk" +hasnt "…and no @id with one" "$hfw_log" "@21junk" +has "…the launcher's well-formed refs standing in for both" "$hfw_log" "; window hfsess:7 @77;" +run env PATH="$A17PATH" LANE_HANDOFF_NO_TMUX=1 \ + WORKBENCHES_CLAUDE_WINDOW_REF="hfsess:7junk" WORKBENCHES_CLAUDE_WINDOW_ID="@77junk" \ + CLAUDE_CODE_SESSION_ID="$HF_ID" CLAUDE_PROFILE_NAME=team-05a "$HANDOFF_CMD" --lane repoHF-83 clear +is "a handoff whose launcher refs are malformed exits 0" "$rc" 0 +hfw_log="$(command grep '^PAUSED' "$LOGD/repoHF-83.md")" +hasnt "…recording neither of them" "$hfw_log" "7junk" +hasnt "…so the record carries no window at all rather than one nothing resolves" "$hfw_log" "; window " + # ------------------------------------- 4. lane-start --agent: the launcher table mkdir -p "$WIP/handoffs/repoAG"