Skip to content

A silent tmux is no longer read as a dead window, the companion verdict is read off the lane's live window from any window, and the guard's cure for a lane named before Rule 4 says what it cannot fill in (#73) - #222

Merged
brettheap merged 5 commits into
mainfrom
fix/rare-state-instructions-73
Oct 18, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Three surfaces printed a wrong instruction in a rare state (#73). Each one is fixed here, with a regression case that fails on f87fd5a and passes on this branch.

What was wrong, with evidence

Measured on f87fd5a in an isolated mini-suite: the suite's own sandbox setup plus the Amendment 12 section's repoGD fixtures. The commands below are run as the suite runs them.

1. A silent tmux read as a dead window. record_window_state returned 1 ("refuted") both when the server does not have the window and when no server answered at all. transcript_holders writes every 1 as (gone), and lane-end --retire reads (gone) as "not the lane's own session". With tmux installed but its server silent:

transcript-holders <id>  → …|window gdsess:@12 (gone)|unrelated|…
lane-end repoGD-1 --retire <the lane's own pid>   (exit 0)
lane-end: pid 538765 IS a live DUPLICATE of lane repoGD-1's own transcript … — proved, and NOTHING has been written or stopped
lane-end:   window gdsess:@12 (gone)  kind interactive  profile t1
lane-end: Amendment 6(d)'s retire act, typed IN THAT SESSION — there is no API to rename one from outside it:
lane-end:     /rename repoGD-1 · retired 2026-10-13

That is Amendment 6(d)'s retitle, printed for the conversation that IS the lane.

2. The companion was judged relative to the asker. transcript_holders marked here only from $LANES_THIS_WINDOW and the pane's process tree. Asked from another window, the harness's bg companion therefore came back unrelated:

from the lane's window  → <bg pid>|bg|none|t1|bg|companion|…
from othersess:@55      → <bg pid>|bg|none|t1|bg|unrelated|…

lane-end --retire <companion> from another window is already refused by name on f87fd5a. #61 added that through its profile fallback (dup_int_profs), after this issue was filed, so lane-end's companion branch does run today. What remained wrong is the read itself: transcript-holders, the read that refusal tells a person to run because it "says which is which", said unrelated for every row.

3. The guard's cure carried a <path> placeholder. Rows 1, 5 and 6 of the (b) table printed lane_start_args straight. A lane named before Rule 4 was therefore told run: lane-start --no-launch --dir <path> legacy-ui. #52 had fixed the offer and left this diagnostic beside it.

What changed

  • Item 1. record_window_state asks a second, untargeted question only when its targeted read came back empty. The new tmux_server_answers runs tmux display-message -p '#{session_name}' with no -t.
    • A server that answers still refutes the window: it stays (gone).
    • A silent server returns the new code 2, which transcript_holders writes as window … (no answer).
    • lane-end's case "$dr_where" already protects any window … that is not (gone), so the lane's own pid is now refused. The refusal names the silence ("tmux did not answer at all … whether that window is still there could not be read") rather than asserting the window is the lane's.
    • Measured against tmux 3.4 on a private socket: with no server, the untargeted read prints nothing and exits 1. A targeted read of an absent @id prints an empty line and exits 0. That is why a status code read off the first question cannot tell the two cases apart.
  • Item 2. When no record is here, transcript_holders reads the companion off the lane's live window record: the interactive record whose window this server resolves and agrees with (rc 0). That is the record lane-end already calls "the lane's session". A kind: bg record in that record's profile is then companion from any window.
    • Only one profile may answer. Two live windows in two profiles leave every row unrelated, as before.
    • The anchor row itself stays unrelated, because it is not the asking window's.
    • Every other caller's behaviour is unchanged:
      • The guard and ssb_name_line read only here and duplicate.
      • In lane-start's dup_check, every outcome is unchanged and only the message differs. The bind call always finds this window's own record here, so the new branch never runs there. The resume branch still refuses on the harness's own fence for any non-interactive record.
      • lane-end and duplicate_holder_pids already treat this set as companions, through the profile fallback.
    • The fallback in lane-end stays as it was, because duplicate_holder_pids applies the very same rule. Only its comments are updated.
  • Item 3. The new guard_refuse_start reuses guard_args_filled. It prints the filled-in command where it can. Where it cannot, it appends the offer's sentence ("lane-start needs that lane's DIRECTORY and nothing in the register, the window or this session says which one it is — the checkout is yours to supply") and prints the offer's own line: lane-start [--no-launch] --dir <that lane's checkout> <lane>.
  • The transcript-holders row format and field order are unchanged: pid US kind US tgt US prof US where US verdict US file. What is new is which verdict a row can get, and the where text (no answer).
  • docs/README-lanes.md documents all three: the guard-table note, the companion sentence and the silent-tmux sentence in the duplicate-read section.

Functions and regions edited

  • lanes-edit.sh: record_window_state: header comment and body, lines 8463-8499 at HEAD
  • lanes-edit.sh: tmux_server_answers: new, lines 8502-8514
  • lanes-edit.sh: transcript_holders: header comment (where text, the companion and unrelated verdicts, lines 8546-8631), variable init (8647-8651), pass 1 (8693-8718), pass 2 (8724-8741)
  • lanes-edit.sh: guard_run: the (b)-table header comment (9337-9339) and the cures for rows 1, 5 and 6 (9731, 9802, 9806)
  • lanes-edit.sh: guard_refuse_start: new, after guard_args_filled, lines 9874-9891
  • lane-end: the --retire session-record block. Comments in the dup_int_profs pass and the dr_where case (1389-1414, 1451-1455), and the own-session refusal, which gains a (no answer) arm (1490-1500). Lines 1107-1300 are not touched.
  • tests/test_lane_helpers.sh:
    • cleanup(): $TH73_PID.
    • The main fake tmux: the $FAKE_TMUX_SILENT seam and an untargeted '#{session_name}' answer, lines 301-312 and 347.
    • Amendment 12 section: two new blocks after the bgtwin companion case (7227-7297), and one after the legacy-ui offer block (7352-7391).
  • docs/README-lanes.md: the guard table ("The table — one row per state") and "The duplicate read (Amendment 18(h))"

Tests

One case per item in tests/test_lane_helpers.sh, Amendment 12 section. 31 assertions in all.

  • Item 1:
    • transcript-holders under FAKE_TMUX_SILENT=1 reads window gdsess:@12 (no answer).
    • With a server that answers but lacks the window (FAKE_TMUX_WINDOWS=""), it still reads (gone).
    • lane-end repoGD-1 --retire <lane's own pid> under a silent tmux exits 2, names the silence, and prints no duplicate and no /rename … · retired.
  • Item 2:
    • From othersess:@55, the bg record beside the lane's live window is companion and the lane's own record stays unrelated.
    • From the lane's own window it is companion, unchanged.
    • lane-end --retire <bg> from the other window still refuses it by name.
    • A bg record in another profile stays unrelated.
    • Two live windows in two profiles leave it unrelated.
  • Item 3:
    • Row 1 (window claude, session legacy-ui), row 5 (superseded id of a new legacy-gd row) and row 6 (an id in no row) each print the offer's sentence and its --dir <that lane's checkout> line.
    • None of them prints --dir <path>.

Fail-before / pass-after, proved in isolation. The full suite was not run locally, per AGENTS.md. Instead, a mini-suite was built from this branch's own test file: the sandbox setup, the Amendment 12 fixtures and the three new blocks, extracted verbatim. It was run with env -u LANES_EDIT -u LANES_WORKSPACE_ROOT -u LANES_REPO -u LANES_LANE:

code under test result
f87fd5a 16 passed, 15 failed: 7 fails in item 1, 1 in item 2, 7 in item 3
this branch 31 passed, 0 failed

The whole Amendment 12 section, run the same way:

code under test result
this branch 241 passed, 0 failed
f87fd5a 226 passed, 15 failed. The failing set is byte-identical to the 15 new assertions

The Amendment 11 section, which holds #39's duplicate-holder and --retire cases, and the Amendment 11 hotfix section, which holds ruling (g)'s companion cases through lane-start, give identical results on both trees. The few failures there come from fixtures defined in sections the extract leaves out, so none comes from this change.

bash -n passes on lanes-edit.sh, lane-end and tests/test_lane_helpers.sh, and the added lines carry no bash-4-only construct.

Left as it is, on purpose

  • who's window column still marks a log-recorded window (gone) when tmux_window_field comes back empty (lanes-edit.sh ~7498). That check runs only for rows with no live record, and for a LIVE row the column comes from the live record and offers the attach. So it prints no instruction against a live conversation.
  • The lane dispatcher's fake tmux (lanebin) answers no untargeted '#{session_name}', so under it an unresolvable window reads (no answer). No assertion in the suite reads that label, and lane-start uses it for display only.

Closes #73

🤖 Generated with Claude Code

Summary by Sourcery

Correct lane diagnostics and retirement safeguards for unverified tmux state, cross-window companion reads, and legacy lane names with unknown checkouts.

Bug Fixes:

  • Distinguish silent or unreachable tmux servers from confirmed-missing windows so lane retirement protects processes when window state cannot be verified.
  • Identify companion processes from the lane's live window rather than the asking window, including requests made from other windows.
  • Prevent guard diagnostics for legacy lane names from emitting unusable <path> placeholders and provide an actionable checkout-supply instruction.

Enhancements:

  • Document the revised tmux, companion-verdict, and guard-refusal behavior.

Documentation:

  • Document silent tmux handling, cross-window companion detection, and unfillable legacy lane-start commands.

Tests:

  • Add regression coverage for silent tmux, cross-window companion detection, ambiguous live-window profiles, and legacy lane guard cures.

…ct is read off the lane's live window from any window, and the guard's cure for a lane named before Rule 4 says what it cannot fill in (#73)

Three surfaces printed a wrong instruction in a rare state (#73).

1. record_window_state answered 1 ("refuted") both for a window the tmux
   server does not have and for a server that never answered, and
   transcript_holders writes every 1 as "(gone)", which lane-end --retire
   reads as "not the lane's own session". Measured on f87fd5a: with tmux
   installed and its server silent, `lane-end repoGD-1 --retire <the lane's
   own pid>` exited 0 and printed Amendment 6(d)'s `/rename repoGD-1 ·
   retired <date>` for the conversation that IS the lane. A second,
   untargeted question (tmux_server_answers) now tells a silent server from
   a refuted window: the record reads "(no answer)", which protects the
   process exactly as "(not asked)" does, and lane-end's refusal names the
   silence instead of claiming a window it could not see.

2. transcript_holders read the companion relation relative to the ASKING
   window, so from a bare shell or another lane's window every holder came
   back `unrelated`, the harness's bg companion included. It is now read off
   the lane's live window record (the interactive record in a window the
   server resolves and agrees with) when nothing is `here`; only one profile
   may answer. lane-end already refused the companion from another window
   through #61's profile fallback, which stays because duplicate_holder_pids
   applies the same rule; what changes is the read that refusal points at.

3. The (b) table's rows 1, 5 and 6 printed `lane_start_args` straight, so a
   lane named before Rule 4 was told `run: lane-start --no-launch --dir
   <path> legacy-ui`. guard_refuse_start fills the command in where it can
   and otherwise says so in the offer's words, printing the offer's own
   `--dir <that lane's checkout>` line.

The row format of transcript-holders is unchanged; only which verdict a row
gets, and the `where` text for a silent server, are new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes three rare-state instruction errors: it fails closed when tmux is silent, resolves companions relative to the lane’s uniquely live window, and avoids printing unfillable directory placeholders for pre-Rule-4 lanes, with documentation and targeted regression coverage.

Sequence diagram for silent tmux duplicate protection

sequenceDiagram
    participant LE as lane-end
    participant RH as record_window_state
    participant TM as tmux
    participant TH as transcript_holders

    LE->>RH: record_window_state(tmux_window)
    RH->>TM: tmux_window_field(window, session_name)
    TM-->>RH: empty
    RH->>TM: tmux_server_answers()
    alt server answers
        TM-->>RH: session name
        RH-->>LE: status 1: window gone
    else server silent
        TM-->>RH: no answer
        RH-->>LE: status 2: window unknown
        LE-->>LE: refuse own-pid retirement
    end
    TH->>RH: record_window_state(tmux_window)
    RH-->>TH: status 2
    TH-->>TH: render window (no answer)
Loading

Sequence diagram for guard refusal with unknown checkout

sequenceDiagram
    participant G as guard_run
    participant R as guard_refuse_start
    participant F as guard_args_filled
    participant O as guard_refuse

    G->>R: guard_refuse_start(reason, lane, flag)
    R->>F: guard_args_filled(lane)
    alt checkout can be derived
        F-->>R: success
        R->>O: guard_refuse(reason, filled lane-start command)
    else checkout unknown
        F-->>R: failure
        R->>O: guard_refuse(reason plus offer sentence, --dir <that lane's checkout>)
    end
Loading

Flow diagram for lane-relative companion detection

flowchart TD
    A[transcript_holders reads transcript records] --> B{Asking window has a record?}
    B -->|yes| C[Use asking record profile]
    B -->|no| D[Find interactive record whose window resolves and agrees]
    D --> E{Exactly one live profile?}
    E -->|no| F[Mark rows unrelated]
    E -->|yes| G[Use lane live window profile]
    C --> H[bg record in profile becomes companion]
    G --> H
    H --> I[Companion is recognized from any asking window]
    C --> J[Anchor record remains here]
    G --> K[Anchor record remains unrelated]
Loading

File-Level Changes

Change Details Files
Distinguish an unreachable tmux server from a missing window and fail closed during retirement.
  • Add a second untargeted tmux probe when a targeted window lookup is empty.
  • Represent unanswered windows as (no answer) while retaining (gone) for server-confirmed absence.
  • Prevent lane-end --retire from treating the lane’s own process as a duplicate when tmux is silent.
  • Add fake-tmux coverage for silent servers and answering servers without the target window.
lanes-edit.sh
lane-end
tests/test_lane_helpers.sh
docs/README-lanes.md
Determine companion status from the lane’s live window rather than the asking window.
  • Track the profile of the uniquely resolved, live interactive window.
  • Mark same-profile background records as companion when no holder is here, regardless of the asking window.
  • Keep ambiguous multi-profile cases and cross-profile records unrelated.
  • Verify lane-end refusal behavior and existing caller semantics remain intact.
lanes-edit.sh
lane-end
tests/test_lane_helpers.sh
docs/README-lanes.md
Make guard refusal instructions explicit when a pre-Rule-4 lane has an unknown checkout.
  • Add guard_refuse_start to reuse filled lane arguments when available.
  • Otherwise explain that the directory cannot be inferred and print --dir <that lane's checkout> instead of <path>.
  • Apply the helper to the affected guard-table rows and test legacy, superseded, and unknown-transcript cases.
lanes-edit.sh
tests/test_lane_helpers.sh
docs/README-lanes.md
Document the revised tmux, companion, and guard behaviors and add regression coverage.
  • Preserve the transcript-holder row format while documenting the new (no answer) state and companion resolution rule.
  • Add 31 Amendment 12 assertions covering all three fixes and cleanup for spawned fixtures.
  • Validate syntax and report isolated fail-before/pass-after results.
docs/README-lanes.md
tests/test_lane_helpers.sh

Assessment against linked issues

Issue Objective Addressed Explanation
#73 Distinguish a silent or unreachable tmux server from a server that answered and refuted a recorded window, so liveness reporting does not label silence as a dead window and lane-end --retire does not offer the lane's own retitle. ❌ The PR adds the second tmux question and protects lane-end --retire by reporting (no answer) in transcript_holders. However, it explicitly leaves who's liveness reading unchanged; that path can still label a window (gone) when tmux did not answer, so the distinction is not applied to all the liveness reporting identified in the issue.
#73 Determine a transcript's companion relative to the lane's live window, rather than the caller's window, so transcript_holders identifies the companion when run from another window or a bare shell. ✅
#73 Avoid printing an unfilled --dir <path> command as the cure for legacy lane names; explain that the checkout must be supplied and print the offer's checkout placeholder instead. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Brett Heap and others added 3 commits October 18, 2026 03:52
… this PR on today's main

One conflict, in tests/test_lane_helpers.sh's `cleanup` trap: #215 (61a7378) added a removal of `$A11T_ROOT`, #180's short checkout root for the clause (c) TAIL case that lives outside `$SANDBOX`, at the same place this PR added the kill of `$TH73_PID`, the live sleep its window-twin case records. Both are kept: the `TH73_PID` kill stays with the other fixture-process kills, and #215's comment and `A11T_ROOT` removal follow it, ahead of the `$SANDBOX` removal. They do not interact, and the resolved file is exactly main's version plus this PR's own change to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… this PR on today's main

No conflict: #209 (2cb14a9) touches tests/test_lane_helpers.sh (header and bin-directory comments, the repoG#8 assertion, a new `lanes` getopt fence case) and docs/README-lanes.md (two wording lines) only in regions away from this PR's hunks, and both files merged as exactly main's version plus this PR's own change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… this PR on today's main

No conflict: of the three commits since 2cb14a9, #238 and #240 touch only park, resume, status and their tests, and #213 touches lanes-edit.sh (managed_seam_refuse, managed_local_hint, managed_local_remedy, lane_trees_list and the managed-projection and lane-trees verbs), docs/README-lanes.md and tests/test_lane_helpers.sh (its managed-seam cases) only away from this PR's functions and hunks, and all three shared files merged as exactly main's version plus this PR's own change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… this PR on today's main

One conflict, in tests/test_lane_helpers.sh, in the FAKE TMUX header: #220's
paragraph on FAKE_TMUX_RENAME_FAIL (its code is the rename-window arm below,
which merged cleanly) and this PR's FAKE_TMUX_SILENT paragraph and block both
insert directly before `case "${1-}" in`. Resolved as #220's independent review
asked: #220's FAKE_TMUX_RENAME_FAIL comment first, then a `#` separator, then
this PR's FAKE_TMUX_SILENT comment and block, both before the `case`. They do
not interact: the silent-server exit answers before any arm runs, and the
rename refusal lives in the rename-window arm. Every line of both sides is kept.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

brettheap added a commit that referenced this pull request Oct 18, 2026
… act on every verified child, not the first (#76, #86) (#205)

Landed on Brett Heap's word "land #205 #222 #223 #239 when green" (2026-10-18), by lane openRepoTools-5. Required Linux CI (guard-launch-mode, tests, tests-no-submodule; run 39148145583) was green on head 4c7e847 with main babd9fe in its tree. Main a059c1a merged with head 4c7e847 is tree 55e51f6, which queue run 39154616302 (workflow_dispatch of tests on cb9da52) tested whole and green.

Independent review (lane 5, Opus, of d4cf2ee) and delta review of de0809c with its polish 0cf7417 confirmed: LAND; the merges of main add nothing to the PR's own lines. The --resume condition is Brett Heap's ruling of 2026-10-18.

Closes #76
Closes #86

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap
brettheap marked this pull request as ready for review October 18, 2026 19:51
@sourcery-ai

sourcery-ai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 23 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit 0f53e40 into main Oct 18, 2026
5 checks passed
brettheap added a commit that referenced this pull request Oct 18, 2026
… being bound, so the offer's yes no longer records a RESUMED nobody is in (#70) (#223)

Landed on Brett Heap's word "land #205 #222 #223 #239 when green" (2026-10-18), by lane openRepoTools-5. Required Linux CI (guard-launch-mode, tests, tests-no-submodule; run 39096012830) was green on head e40c067 with main 2cb14a9 in its tree. Main 0f53e40 merged with head e40c067 is tree d2da196, which queue run 39154627955 (workflow_dispatch of tests on 2109b12) tested whole and green.

Independent review (lane 5, Opus, of a4acad3) and delta review of e40c067: LAND; the merges of main add nothing to the PR's own lines.

Closes #70

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
…otes, and the suite stops counting its bash files (#54 part b, #58) (#239)

Landed on Brett Heap's word "land #205 #222 #223 #239 when green" (2026-10-18), by lane openRepoTools-5. Required Linux CI (guard-launch-mode, tests, tests-no-submodule; run 39136612565) was green on head 7c4df04 with main 586dea2 in its tree. Main 7748375 merged with head 7c4df04 is tree 8c13769, which queue run 39154634006 (workflow_dispatch of tests on 1d149e3) tested whole and green.

Independent review (lane 5, Opus, of bb3c7dc): LAND; the later commits 5f1c329 and 7c4df04 change comments only.

Closes #54
Closes #58

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant