Repository navigation
The handoff skill's two PAUSED writes carry the why it computed, sanitized as lane-handoff's sanitize does, and a why carrying a line break is refused before step 1 (#80 item 7, #85) - #264
Merged
Conversation
… carry the computed why, and a why with a line break is refused before step 1 (#80 item 7, #85) Checkpoint made on the lane 5 coordinator's word ahead of the 13:06Z Eagle reboot. This is not the finished slice. Done: - skills/handoff/SKILL.md: a new paragraph and sh block before step 1 refuse a $why carrying CR or LF (exit 2), with lane-handoff's door words. Step 4's why_text is now sanitized as lane-handoff's sanitize does (CRLF, CR and LF each one "; ", and every " — "), with an empty why written "handoff". Writes (a) and (b) carry $why_text in place of the template's placeholders, and (b) is lane-handoff's own register line. Step 4's prose says so. - tests/test_handoff_skill_80_85.py (new): runs the door block and step 4's block under bash and zsh -f against a stub helper, and compares them with lane-handoff's own sanitize and register-line template. - Checked: every skill shell block parses (bash -n via test_skill_shell_blocks.parse_problems: none), zsh -n on the door block, and py_compile on the test. Not done: - The RED run (the new test against 5e88a2c's skill) and the GREEN run never executed: both were queued on the shared pytest lock and were killed for the reboot. - No PR has been opened, and CI has not run. - Merge main once #260 lands (merge, never rebase). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
Reviewer's GuideThe PR fixes the handoff skill’s PAUSED records to carry the actual sanitized why, adds lane-handoff-compatible handling for empty and separator-containing values, and rejects multiline whys before any side effects; a new isolated 39-case test suite validates behavior and parity across supported shells. Sequence diagram for safe handoff why handlingsequenceDiagram
participant Operator
participant HandoffSkill
participant LaneHandoff as lane-handoff
participant HandoffState as Handoff state
participant Records as PAUSED records
Operator->>HandoffSkill: /handoff [why]
HandoffSkill->>HandoffSkill: Check $why for CR or LF
alt multiline why
HandoffSkill-->>Operator: Refuse with exit 2
Note over HandoffSkill: No rename or write occurs
else one-line why
HandoffSkill->>HandoffState: Refresh and commit handoff
HandoffSkill->>HandoffSkill: Sanitize why and append kind
HandoffSkill->>Records: log PAUSED with $why_text
HandoffSkill->>Records: append-line with $why_text
end
Flow diagram for handoff why sanitizationflowchart TD
A[Read operator why] --> B{Contains CR or LF?}
B -->|Yes| C[Refuse before step 1]
B -->|No| D[Replace every — with ; ]
D --> E{Why is empty?}
E -->|Yes| F[Use handoff]
E -->|No| G[Keep sanitized why]
F --> H[Append kind word]
G --> H
H --> I[Write PAUSED object log]
I --> J[Write PAUSED register line]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…instead of a copy of it SonarCloud's quality gate failed on #264 with 5.9% duplication on new code (3% allowed). Its two duplicated blocks were the sandbox's PATH and environment setup and its run method, both copied from tests/test_handoff_skill_72_80.py's Sandbox. The test now subclasses that Sandbox and imports its SHELLS, the way that file imports test_skill_shell_blocks.py's fence reader. It adds only the recording stub, tmux and date. The reader of the recorded calls is renamed recorded(), because the parent keeps a `calls` attribute that would shadow a method of that name. No case changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nder set -u, and the #80/#85 test names b707bd7 as its red base A fix round on #264, from its independent review. main 55aae16 (#246) is merged in before this commit. The check before step 1 was `case "$why" in`. In a shell running under `set -u`, with no why given, that stopped the shell there: bash said "why: unbound variable" and zsh said "parameter not set". It now reads "${why-}". An unset why is no why at all: it passes, and step 4 writes it "handoff", as lane-handoff does. The paragraph above the block says so. tests/test_handoff_skill_80_85.py gains one case, under bash and zsh -f. It runs the check and then step 4 in one `set -u` shell with why unset, and expects no error and "handoff kind unknown" in both writes. The block's marker is now its refusal text, so the case finds the old block and fails on the real error. The docstrings now name b707bd7, where the red run was measured, instead of 5e88a2c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This was referenced Oct 19, 2026
brettheap
marked this pull request as ready for review
October 19, 2026 21:51
|
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 1 day and 20 hours by commenting |
This was referenced Oct 19, 2026
brettheap
pushed a commit
that referenced
this pull request
Oct 20, 2026
…271) into act 2's slice 1a (#198) One conflict, in lanes-edit.sh's lane_state_follow, at the check of a snapshot this helper cannot read: this branch releases the workstation's sidecar lock (sidecar_release); main released W's mutex where the follow took it (lsf_own) and gave a new note naming $LSS_WHY. Resolved as the PR says: keep sidecar_release, take main's note. No line of main's that merged on its own uses lsf_own or release_lock inside lane_state_follow, and main's lanes-edit.sh changes since 55aae16 add no lock call. 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
#80 item 7: the two record writes carried the template, not the value. Step 4 of
skills/handoff/SKILL.mdcomputeswhy_text(the operator's why, then the kind's word). Then it writes neither record with it:I ran step 4's own block, as main
b707bd7prints it, against a stublanes-edit.shwithwhy="a usage reset". These are the two writes it made:lane-handoffis the same act from a shell (Amendment 17(a)). It hands both writes its computed$why_text:lane-handoffL2835 (thelog PAUSEDcall) and L2850 (theappend-line) on main55aae16.#85, the skill's half: a line break in the why. #218 did three things:
append-linerefuses a CR or LF before it writes.lane-handoffrefuses a why carrying one right after reading its arguments, before anything is renamed or written.lane-handoff'ssanitizespells every break;, as the second line of defence.The skill did none of these:
$why.why_text="$why"sanitized nothing. It did not even replace—, which the step's own prose says it does.So a why with a break reached step 2's top block, where a
---line ends the promptlane-starthands the next session. Then both step 4 writers refused it, but only after step 2 had refreshed and committed the handoff. The act ended with a top block and no record behind it.What changed
All in
skills/handoff/SKILL.md:shblock between the preamble's fence and## 1.refuse a$whycarrying a newline or a carriage return. The refusal exits 2, and nothing has been renamed or written at that point. It useslane-handoff's own door words ("the why is ONE line", "with '; ' where the break was").lane-handoff'ssanitizedoes. CRLF, a lone CR and a lone LF each become one;, and so does every—. An empty why is writtenhandoff, aslane-handoffwrites it. The kind's word follows, as before. This is the second line of defence: where a break arrives anyway, the record is still written, on one line. Step 4's opening prose now says this.$why_text. Write (a)'s free text is"$why_text". Write (b) islane-handoff's own register line, word for word:PAUSED — lane $lane, session $uuid@$ws, <UTC>, $why_text; handoff refreshed.Both writes drop two pieces of template wording, to match
lane-handoff's own lines:<what's open, or NOTHING CLAIMED>segment, from (b).lane-handoff's lines never carried either, and nothing in the skill computes a value for the second. What is open is for the handoff's state line to say. The independent review checked that no tool reads either piece:lanes-index,lanes-edit.sh'sRULE6_AWK,lane-end'sNOTHING CLAIMEDscrub (which reads the row's state cell only),lane-start, andappend-line'slane_from_text. A grep of the tools on this branch agrees. This is a choice about what the record carries; the alternative is to compute a claims summary in both writers.Main
b707bd7, then55aae16(#246), is merged in (merges, never a rebase). #260'ssread15(d) arm, itscanon-laneread after step 1's rungs and its whole-string window checks are unchanged, and they still sit in front of step 4's writes. #246 does not touch the skill. Itslane-handoffchanges moved the lines the tests cut out (sanitize, the kind-word lines and the register-line template), and each is still found exactly once.Functions and regions edited:
skills/handoff/SKILL.md: new paragraph and sh block between the preamble's L=/sread fence and "## 1."(≈L120-139 at the head)skills/handoff/SKILL.md: step 4's opening prose paragraph(≈L492-499)skills/handoff/SKILL.md: step 4 block, the why_text lines after payload="$payload; kind $kind"(≈L688-703)skills/handoff/SKILL.md: step 4 block, write (a)'s free-text argument and write (b)'s append-line text(≈L737, ≈L743)tests/test_handoff_skill_80_85.py(new file)Not touched:
sread, step 1's block, the step-4 window block (#260's), andlane-handoff.Tests
tests/test_handoff_skill_80_85.py, new, 41 cases, in the style oftests/test_handoff_skill_72_80.py. It importstests/test_skill_shell_blocks.py's fence reader and runs the skill's own blocks underbashandzsh -f. The environment is an explicit dict with a stublanes-edit.shthat records each call's arguments NUL-separated, atmuxthat answers nothing and adatethat answers one instant.<placeholder>.lane-handoff: for seven whys, the skill's log free text and register line equal whatlane-handoff's own code produces. That code is itssanitizefunction, its kind-word lines and its register-line template, all cut out oflane-handoffitself. The seven are a plain why, a—,respawn,in-process, an empty why,a\r\nb\rc\nd — eand a trailing LF.append-lineproves its one line AFTER writing it, so alane-handoff --whycarrying a newline corrupts LANES.md and leaves the checkout dirty #85, step 4:a\r\nb\rc\nd — eis writtena; b; c; d; e kind unknown, and no write is handed a CR or LF.append-lineproves its one line AFTER writing it, so alane-handoff --whycarrying a newline corrupts LANES.md and leaves the checkout dirty #85, the check:—, an empty why and no why pass silently.set -u, an unset why passes the check with nothing said, and step 4, run in the same shell, writes ithandoff kind unknownin both writes (added in the fix round).## 1.and before every block whose code renames, publishes, commits or writes.Fail-before / pass-after. Every
LANES_*,TMUX*andWORKBENCHES_*variable was unset. Each run was a plainflock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest -q -p no:cacheprovider …, started detached withsetsid -f.b707bd7exported bygit archive, with this test copied in. Result:39 failed in 8.22s, every case. 18 assertions showon <operator>'s word: <sanitized verbatim>where the computed why belongs, and 17 find no check block (found 0).385 passed, 1 failedat the merge commit:test_handoff_skill_80_85.py,test_handoff_skill_72_80.py,test_skill_shell_blocks.py,test_handoff_skill_commit_block.py,test_handoff_skill_98_99_100.pyandtest_repo_hygiene.py. The one failure was this file's door-position case, which matchedlane-handoffinside a comment 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 added to the preamble's fence. Commitc02e267makes that case read only the lines that run, and the file then gives39 passed in 20.06s. Commit9b80eedmoves the test ontotest_handoff_skill_72_80.py'sSandboxinstead of a copy of it, because SonarCloud's gate counted the copied sandbox as 5.9% duplicated new code (3% allowed). No case changed. With the file at head9b80eed:98 passed in 42.01sfor this file,test_handoff_skill_72_80.pyandtest_skill_shell_blocks.py;b707bd7with the file copied in:39 failed in 10.80s, for the same reasons as above.py_compileon the test, andbash -non every skill shell block throughtest_skill_shell_blocks.parse_problems(no problems).I did not run the full suite locally (the AGENTS.md ruling of 2026-09-16; the box is shared). CI's
testsjob is the suite of record.Fix round, after the independent review (LAND at
9b80eed)${why-}.case "$why" instopped a shell running underset -uwhen the operator gave no why: bash saidwhy: unbound variableand zsh saidparameter not set. The paragraph above the block now says that an unset why passes and step 4 writes ithandoff, underset -utoo. The test's marker for the block is now its refusal text, so the fail-before run finds the old block and shows the real error.set -u, in bash andzsh -f, withwhyunset.5e88a2c.lane-handoffL2835/L2850 on55aae16, says both writes drop the "on 's word" wording as well as the claims segment, and carries a squash message.55aae16(Make the lane tools look inside a triad's legs and paired feature trees, so the sweep, lane-end's gate, lane-handoff, lane-reconcile and the daily report can see a lane's work there, and change nothing a checkout with no project.yaml does #246) is merged in.Fix-round runs, each a plain locked pytest started with
setsid -f, with every seam unset:f040eaa(9b80eed with main 55aae16 merged in), exported with this round's test copied in:2 failed, 39 passed in 19.66s. The two failures are the new case: bash exits 127 withline 9: why: unbound variable, and zsh sayswhy: parameter not set.b707bd7:41 failed in 11.66s.a7c87c5:388 passed in 81.90sfortest_handoff_skill_80_85.py,test_handoff_skill_72_80.py,test_skill_shell_blocks.py,test_handoff_skill_commit_block.py,test_handoff_skill_98_99_100.pyandtest_repo_hygiene.py.Squash message
Title:
Body:
#80: every item, and what did it
By lane 5's triage, checked against
gh issue view 80 --comments(eleven findings: seven in the body, two in each later comment, no others):lane --dirswallows a following-*be0743b)lane-handoffrenames the window before its fallible record writesbe0743b)lane-handoff's window-id checks are prefix globsbe0743b)add5c15)lane-handofffcccbe0(#47): the Amendment 17 section oftests/test_lane_helpers.shruns$HANDOFF_CMD; recorded in #220lane-handoff --lane=accepts an empty valuebe0743b)lane-handoff --writers-file=accepts an empty valuebe0743b)00971b9):publish-handoffstages beside the target and installs withmv -f; recorded in #220have_tmuxdoes not check the process is inside tmuxbe0743b)tmux rename-windowis swallowedbe0743b)#85: every part, and what did it
append_text_line(and theappend-lineverb) refuses CR/LF before it writes01eff2c)lane-handoffrefuses a why carrying CR/LF at the door; itssanitizeflattens CR/LF01eff2c)lane-handoffwith a newline in the why leaves the register clean;append-linerefuses with nothing written01eff2c)/handoffskill's copy: refused before step 1, sanitized in step 4 aslane-handoff'ssanitizedoes#218 listed the skill's half as the one part remaining for #85, and #85's comments hold only claims.
Closes #80
Closes #85
🤖 Generated with Claude Code