Repository navigation
The handoff skill's step-1 fence refuses Amendment 15(d)'s exit 2 instead of reading it as no answer, and its window ref is checked as a whole string (#72 item 8, #80 item 4) - #260
Merged
Conversation
…tead of reading it as no answer, and its window ref is checked as a whole string (#72 item 8, #80 item 4) #72 item 8: `sread`'s `8|2) sr_out=""` arm read every exit 2 as "no answer" and fell to the next rung. But 2 is also `canon_lane`'s refusal of a register holding two rows that differ only by case (Amendment 15(d)), which `register-row` carries. Falling through it bound the window to whatever the next rung answered, and the last rung is the workstation's newest swap: a different lane, which the skill would then rename the window for and write a PAUSED record and a restart command about. `lane-handoff`'s own `sread` already tells the two apart by the helper's words. The skill now does the same: a 2 whose stderr carries "Amendment 15(d)" stops the skill (exit 2) and relays the helper's words, and any other 2 still falls through, as from an old helper. #80 item 4: step 4 held the live `#S:#I` and `WORKBENCHES_CLAUDE_WINDOW_REF` to `case "$win" in *:[0-9]*)`, a prefix glob, so `s:1junk` (or `:1`, or a value with a newline after the index) went into the append-only `window` sub-field, where `window-lane` can never resolve it. Both checks are now `[[ "$win" =~ ^.+:[0-9]+$ ]] || win=""`, the form the `<@id>` check beside them already uses, and the same rule as `lane-handoff`'s `window_ref_ok`. tests/test_handoff_skill_72_80.py extracts both blocks from the skill and runs them under bash and zsh -f with a stub lanes-edit.sh and a tmux function. 15 of its 35 cases fail against 08e7550's skill and all 35 pass with this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR hardens the handoff skill by stopping safely on Amendment 15(d)'s ambiguous-register refusal while preserving legacy exit-2 compatibility, and by enforcing whole-string validation for live and exported tmux window references; the new 35-case shell tests exercise both fixes under Bash and zsh. Flow diagram for handoff read refusal handlingflowchart TD
A["sread invokes lanes-edit.sh"] --> B{"Exit status?"}
B -->|0| C["Use answer"]
B -->|8| D["Treat as no answer and continue"]
B -->|2| E["Read stderr again"]
E --> F{"Contains Amendment 15(d)?"}
F -->|Yes| G["Print REFUSED and exit 2"]
F -->|No| D
B -->|Other| H["Print REFUSED and exit 1"]
Flow diagram for whole-string window reference validationflowchart TD
A["Read live tmux window ref"] --> B{"Matches ^.+:[0-9]+$?"};
B -->|Yes| D["Use live ref"];
B -->|No| C["Discard live ref"];
C --> E["Read WORKBENCHES_CLAUDE_WINDOW_REF"];
D --> F{"Validate selected ref"};
E --> F;
F -->|Valid| G["Use window ref"];
F -->|Invalid| H["Clear window ref"];
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…so a case twin from window-lane or the newest swap refuses too, and a window ref carrying a line break is dropped (#72 item 8, #80 item 4, review of #260) The independent review of #260 measured the gap c9d681f left. Step 1 refused a case-twin lane only where rung 1 found it, because only rung 1 reads its name through `register-row` and so through `canon_lane`. Rungs 2 and 3 answer with a record's lane field and the swap row's first cell, which nothing read through the resolver. With window `zsh`, `register-row` 8, `swapped` naming `other-7` and the register holding `other-7` and `Other-7`, step 1 printed `lane=other-7` and exited 0. `lane-handoff` closes this by reading the bound name through `canon-lane` after its rungs. The skill now does the same through its fence: Amendment 15(d)'s 2 refuses, an old helper's 2 keeps the name, a failed read (66, an alias table that could not be read) stops the skill, and the register row's spelling is taken. The window ref's regex `^.+:[0-9]+$` lets `.` match a line break, so `junk<LF>s:1` passed it, and `log PAUSED` refuses a payload carrying a newline or a carriage return. A value with either is now dropped beside each regex check, in a `case` that works in bash and zsh. The comment that called the fence `/restart`'s `lread` with one difference now names the second (the 15(d) refusal), and SonarCloud's two composite asserts in the test are split. tests/test_handoff_skill_72_80.py gains 18 cases (rungs 2 and 3, canon-lane's answers, three line-break refs). All 18 fail against c9d681f, 33 of the 53 fail against 08e7550, and all 53 pass with this change, under bash and zsh -f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
brettheap
added a commit
that referenced
this pull request
Oct 19, 2026
… cause and both documented rows, so a die that claims nothing was pushed or a fourth die no row documents fails it (#68 item 5) (#259) Landed on Brett Heap's word "land #259 #260 #261 when green" (2026-10-19), by lane openRepoTools-5. Required Linux CI (guard-launch-mode, tests, tests-no-submodule; run 39171682413) was green on head adb6940 with main 08e7550 in its tree. Main 5e88a2c merged with head adb6940 is tree afe4dc7, which queue run 39248744253 (workflow_dispatch of tests on fb9c0d8) tested whole and green. Independent review (lane 5, Opus, of 25ae36d): LAND. Fix round 1 (adb6940) took its non-blocking points: the timeout-call reader reads bare and quoted operation words, the exit-3 die pattern takes any spacing, and the anchor wording is exact. Part of #68 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
marked this pull request as ready for review
October 19, 2026 13:02
|
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 6 hours by commenting |
brettheap
added a commit
that referenced
this pull request
Oct 19, 2026
…, and prints both in its transcript and in CI's step summary (#77, step 1a) (#261) Landed on Brett Heap's word "land #259 #260 #261 when green" (2026-10-19), by lane openRepoTools-5. Required Linux CI (guard-launch-mode, tests, tests-no-submodule; run 39245315763) was green on head d05dc0b with main 08e7550 in its tree. Main add5c15 merged with head d05dc0b is tree ca3c9d5, which queue run 39248755273 (workflow_dispatch of tests on 800ec88) tested whole and green. Independent review (lane 5, Opus, of 6335ca8): LAND. Fix round 1 (d05dc0b) reports the teardown once and names a failed removal, and moves the rows-equal-headings check to the report. Part of #77 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 19, 2026
…tized as lane-handoff's sanitize does, and a why carrying a line break is refused before step 1 (#80 item 7, #85) This commit and the WIP checkpoint before it (520b98f) are one slice. Main b707bd7 is merged in, so #260's sread 15(d) arm, its canon-lane read after step 1's rungs and its whole-string window checks sit in front of step 4's writes, unchanged. #80 item 7: step 4 of skills/handoff/SKILL.md computed why_text and then wrote neither record with it. The object log's PAUSED free text was the literal "on <operator>'s word: <sanitized verbatim>", and the register's file-level line carried that and "<what's open, or NOTHING CLAIMED>", so running the block as printed recorded the placeholders. Both writes now carry $why_text, and the register line is lane-handoff's own line word for word, which is what "the same act writes the same record" (Amendment 17(a)) asks. That drops the "<what's open, or NOTHING CLAIMED>" segment, which lane-handoff's line never carried. #85, the skill's half: #218 made append-line and lane-handoff refuse a why carrying CR or LF before anything is written, and made lane-handoff's sanitize spell every break "; ". The skill did neither. Its why_text was "$why" as typed, so it did not even replace " — ", though its own prose says it does. A why with a break would reach step 2's top block, and then both step 4 writers would refuse it after step 2 had already committed the handoff. The skill now refuses such a why before step 1, with the words lane-handoff uses at its own door. Step 4 then sanitizes the why as lane-handoff's sanitize does (CRLF, CR and LF each one "; ", and every " — "), with an empty why written "handoff". That is the second line of defence. tests/test_handoff_skill_80_85.py runs the door block and step 4's block under bash and zsh -f against a stub helper. It compares the skill's two writes with what lane-handoff's own sanitize, kind-word lines and register-line template produce, all cut out of lane-handoff. This commit makes the door-position case read only the lines that run. After the merge, #260's comments in the preamble's fence cite lane-handoff by name, and a comment that names an act is not that act. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 19, 2026
Main now holds lane 4 queue, lane 5 queue (#259 #260 #261) and #246 (55aae16). The one conflict is tests/test_repo_hygiene.py import block: #262 adds import sys and #259 adds from collections import namedtuple; both are kept. The tree is lane3-queue/q1d-228pr1-on-p246c, run 39256880821, green. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 19, 2026
Main moved past this branch's base 5e88a2c: #259, #260, #261 and #246. #246 touches lanes-edit.sh and docs/README-lanes.md too. The merge is clean, the lock lines are main's own, and lanes-edit.sh parses under bash 3.2.57 and 5.2. CI tests the moved main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What was wrong
#72 item 8: the skill read every exit 2 as "no answer". Step 1 of
skills/handoff/SKILL.mdreads the register throughsread, and its arm at 08e7550 was:But exit 2 has two meanings. It is what a
lanes-edit.shpredating a subcommand returns for an unknown one. It is alsocanon_lane's refusal of a register holding two rows that differ only by case (Amendment 15(d)), whichregister-rowcarries (lanes-edit.sh'sregister-row)arm:lane="$(canon_lane "$lane")" || exit 2). Falling through that refusal goes to the next rung. The last rung is the workstation's newest swap, a different lane, and the skill then renames the window for it and writes a PAUSED record and a restart command about it.lane-handoff's ownsreadalready tells the two apart by the helper's words (its2)arm, taken in Copilot round 2 on #47). The skill had not caught up.Measured against 08e7550 with a stub helper: window
repo-1,register-rowexits 2 with Amendment 15(d)'s words,window-laneexits 8, andswappedanswersother-7. Step 1 then printedlane=other-7.#80 item 4: the window ref was checked by a prefix glob. Step 4 held the live
#S:#Iand the launcher'sWORKBENCHES_CLAUDE_WINDOW_REFtocase "$win" in *:[0-9]*). Sos:1junk,:1, and a value with a newline after the index all went into the append-onlywindowsub-field.window-lanecan never resolve them there, because a live#S:#Ialways ends in an index, which is digits.Fix round (the independent review of c9d681f). The first commit left two gaps.
register-row, and so throughcanon_lane. Rungs 2 and 3 answer with a record's lane field and the swap row's first cell, and nothing read either through the resolver. The reviewer's case: windowzsh,register-rowexits 8,swappednamesother-7, and the register holdsother-7andOther-7. At c9d681f, step 1 printedlane=other-7and exited 0.lane-handoffcloses this withsread lane_canon … canon-lane "$lane"after its rungs..in^.+:[0-9]+$matches a line break, sojunk<LF>s:1passed.log PAUSEDrefuses a payload carrying a newline or a carriage return, so such a value costs the whole record, not just the sub-field.What changed
sreadgains a2)arm. It asks the same read again with only stderr kept, aslane-handoffdoes. If those words containAmendment 15(d), it prints aREFUSED:line naming the read, relays the helper's words, and stops the skill with exit 2. Any other exit 2 falls through with the answer empty, as before. 8, 0 and every other code behave as before.2is a refusal, and gains the "2is two answers" paragraph that matcheslane-handoff's. The comment calling the fence/restart'slread"with one difference" now names the second difference:lreadstill reads every 2 as no answer.canon-lane(fix round). Whichever rung answered, the name goes through the samesread, aslane-handoffdoes:canon-lanepasses through rather than flattening to 2.[[ "$win" =~ ^.+:[0-9]+$ ]] || win="". This is the form the<@id>check beside them already uses, and the same rule aslane-handoff'swindow_ref_ok. In the fix round, each check also getscase "$win" in *$'\n'*|*$'\r'*) win="" ;; esac, which works in bash and zsh. A comment above the block says why. A live value that is dropped falls back to the export, as an empty one did.No other test or doc quotes the changed lines. The shell suite's
sfence_probeandskill_win_ofcut these blocks by awk ranges, and both ranges still match.Functions and regions edited
skills/handoff/SKILL.md: the step-0shblock, its comment abovesread()andsread()itself (lines 62-116 at c249ba6)skills/handoff/SKILL.md: step 1's block, a newcanon-laneread after the three rungs (lines 203-219), and the prose paragraph under the block (lines 225-228)skills/handoff/SKILL.md: step 4'swindowsub-field block: the comment abovewin="$(tmux display-message -p '#S:#I', the two shape checks and the two line-break checks (lines 539-555)tests/test_handoff_skill_72_80.py: new fileThis keeps out of #207's lines (
SKILL.md52-58, 126, 235-301 and 551-594 at 08e7550; this PR's hunks at 08e7550 numbering are 62-85, 177-183 and 493-497).git merge-tree --write-treeof c249ba6 is clean with #207's head51909feand withorigin/main9b94a59.Tests
New file
tests/test_handoff_skill_72_80.py(53 cases). It finds the blocks withtest_skill_shell_blocks.py's ownfenced_blocks, cuts them by the shell suite's ranges, and runs them underbashandzsh -f.lanes-edit.shis a stub on a privatePATH,tmuxis a shell function, and the environment is an explicit dict, so noLANES_*,TMUX*orWORKBENCHES_*value is inherited.LANES_NO_FETCH=1. An old helper's 2, a silent 2, 8, 0 and a failed read each behave as before.window-laneorswappedis asked.window-lane) or rung 3 (swapped) refuses atcanon-lane.canon-lane's other answers: the row's spelling is taken; a failed read (66) stops the skill; the same name, or an old helper's 2, keeps the lane.s:1junk(live and exported), a rejected live value falling back to the export,:1, a newline after the index,s:1:2,s:and@12junkfor the id.Fail-before and pass-after, locally. Each run used
flock … python3 -m pytest, with everyLANES_*,TMUX*andWORKBENCHES_*unset.SKILL.mdatcanon-laneanswers, three line-break refs, each under bash and zshAlso at c249ba6:
tests/test_skill_shell_blocks.py,tests/test_repo_hygiene.pyandtests/test_handoff_skill_commit_block.py: 233 passed.sfence_probeandskill_win_ofcases (tests/test_lane_helpers.sh, the lines that read these blocks, run on their own): 18 passed, 0 failed.bash -nandzsh -npass on all three edited blocks.Left as they were
skills/restart/SKILL.md'slreadhas the same8|2)shape. Ten hardenings Copilot rounds 4–6 raised on #47, filed rather than taken: the test lock's ownership, three signal/status gates, a deferred command that drops --agent, and the handoff skill catching up with its command #72 item 8 names only the handoff skill.lane-dirread in this skill also has8|2) dir="". A lane with a case-twin row is now refused in step 1, whichever rung bound it, so step 4 is never reached for one.Part of #72 (item 8)
Part of #80 (item 4)
🤖 Generated with Claude Code