Skip to content

Amendment 23 act 2, slice 3: the handoff skill locates its file with handoff-path and writes it with ONE write-handoff --whole call from the lane's own checkout, so it spells, commits in, pulls and pushes no checkout of its own (#198) - #311

Draft
brettheap wants to merge 1 commit into
feat/198-a23-slice-2a-handoff-verbsfrom
feat/198-a23-slice-3-skill

Conversation

@brettheap

@brettheap brettheap commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-2 (openRepoShape-2)
Claim: #198 (comment)

Slice 3 of Amendment 23 act 2: skills/handoff/SKILL.md stops committing, pulling and pushing the handoff itself. It says where the lane's handoff is with handoff-path, reads the digest of what is there, and writes the complete new file with ONE write-handoff --whole call, which slice 2a added. The block it replaces was one of the three writers that commit to a checkout with no lock and then pull and push (finding 282 of lane openRepoTools-3's review of #307, with lane-start's stamp and lane-handoff's PAUSED commit). The other two are slice 2b's.

Stacked on

Slice 2a's branch feat/198-a23-slice-2a-handoff-verbs at e2037b9, the head this branch was cut from. This PR's own diff is git diff e2037b9 HEAD: one commit, b599ed9, two files. Slice 2a stacks on slice 1b (#307, a draft whose review asks for changes before it lands) and slice 1a (#282).

Decisions

F1 to F9 are numbered as the work's own log numbers them. F2 and F7 are the two that depart from the plan.

  1. F1. Two blocks in step 2, and the verb is called once. The plan's one call needs the prior digest read BEFORE the session writes the complete new file, and the call itself comes AFTER the top block's template. So:

    The old publish-handoff block (the plan's :209) is gone, and the three variables it named, which no step of the file ever defined, are now defined.

  2. F2. DEPARTS FROM THE PLAN: handoff-path "$lane" --create, where the plan says a bare handoff-path "$lane". The skill must READ the file: for the digest, and for the old bytes the new file keeps under ---. A bare handoff-path names a path in a checkout that may not exist yet, because a lane that never wrote through its own checkout has none. --create is handoff-path's own flag (2a's decision 9), and it is moot where routing is off or LANES_REPO is explicit. Question 1.

  3. F3. A handoff that is not found does not stop the swap (R-A11-11). Block 1 prints HANDOFF NOT FOUND, empties handoff_file, and block 2's guard (handoff_file, prior_sha256 and a non-empty prepared_file) prints HANDOFF NOT WRITTEN ... was NOT called. Steps 3 to 5 still run. The existing "if $handoff came back empty, stop here" paragraph is kept: that is a row that does not split, which is a different case.

  4. F4. Every exit of the verb is said, and none stops the swap. Block 2's case:

    • 0: handoff written;
    • 2 with stdout: HANDOFF NOT COMMITTED. The file is written and uncommitted; do not call again, because a second --whole is refused (7);
    • 2 with no stdout: HANDOFF NOT WRITTEN;
    • 3: PUSHED, NOT CONFIRMED;
    • 6: NOT PUSHED, the commit kept on HEAD and named;
    • 7: NOT WRITTEN. The handoff moved, or a restart owns it. Retry only if it moved;
    • anything else: NOT PUBLISHED, and whether the file is written.

    A non-empty stdout means the file is written: cleanup prints HANDOFF_OUT on every exit once it is set.

  5. F5. The subject is built from variables, never from placeholders (the rule of Copilot's review of workBenches#91 against the vendored bytes at f98d734: --dir flag swallows -*, lane-handoff's rename-before-commit and permissive window checks, --lane= accepts empty, and two placeholder leaks in handoff/SKILL.md #80's item 7): handoff( and the lane, then an at sign and the workstation (none where step 1 found none), then ): PAUSED, and the why in the operator's words (handoff where there is none). Control characters are removed (LC_ALL=C tr -d '[:cntrl:]'), because the verb exits 64 on one and a tab in a why would cost the swap its handoff. The em dash survives.

  6. F6. The skill spells no checkout path at all. The file holds no ~/projects/brett-wip, no workspace-root, no LANES_REPO, no LANES_BRANCH and no cd command. No line anywhere says pull with --rebase, so the file can join hygiene's _NO_PULL_FILES (F7).

  7. F7. DEPARTS FROM THE PLAN: _NO_PULL_FILES in tests/test_repo_hygiene.py is NOT edited here. The plan wants skills/handoff/SKILL.md in that tuple, but slice 2b edits the same tuple line for lane-start and lane-handoff, so two slices would conflict on one line by construction. The new module carries test_the_skill_never_pulls_with_rebase_outside_a_comment, which holds the same rule with the same pattern. The third name goes in at the merge of 2b. Question 2.

  8. F8. Step 4: append-line and set-row-state now run as LANES_LANE="$lane" "$L" .... log PAUSED already did. A comment in the block names why: under Amendment 23 S2 a register write goes through the lane's own checkout, and append-line finds its lane from the variable first, so a line that names no lane with a row is not a write with no lane.

  9. F9. The tests are one rewritten module, tests/test_handoff_skill_commit_block.py (61 tests, each of the blocks under bash and under zsh where it is installed). See "Tests".

What changed

Tests

One pytest process per run, with every LANES_*, TMUX*, CLAUDE_* and GIT_* variable unset, a refusing gh stub first on PATH, GH_TOKEN=bogus, GIT_SSH_COMMAND=false and GIT_CONFIG_GLOBAL=/dev/null. Nothing reached a real register, W or GitHub. The two files below were run on a tree whose SKILL.md and test module are byte-identical to b599ed9's (sha256 compared with the commit's files).

file result at
tests/test_handoff_skill_commit_block.py 61 passed b599ed9's bytes
tests/test_handoff_skill_98_99_100.py 29 passed b599ed9's bytes
tests/test_handoff_skill_80_85.py, tests/test_handoff_skill_72_80.py and tests/test_skill_shell_blocks.py, one process 100 passed b599ed9's bytes
tests/test_repo_hygiene.py 253 passed b599ed9's bytes
tests/test_handoff_skill_commit_block.py, with 2a's lanes-edit.sh, docs/README-lanes.md and tests/test_a23_write_handoff.py from 68014b4 overlaid in a copy 61 passed b599ed9 plus those three files

The 61 tests of the module:

  • The plan's five named cases (the first three are counted here, the fourth under "Text rules" and the fifth under "Integration"):
    • test_block_calls_write_handoff_with_whole_and_expect_digest (2): the stub lanes-edit.sh answers handoff-path and write-handoff and logs its argv, its LANES_LANE and the prepared file's bytes; the call is --whole <prepared> --expect-digest <sha> --subject <s> and nothing else;
    • test_no_spelled_checkout_and_no_workspace_root_fallback (2);
    • test_step4_writes_carry_LANES_LANE (4), with the ambient LANES_LANE unset and set to another lane;
    • test_refusal_text_keeps_R_A11_11;
    • the integration case below.
  • One test per exit of the verb (9 exits, 18 tests with both shells): each says what it did and what it did not do, the block exits 0 and the swap's next line still prints, and write-handoff is called once.
  • The subject is one line with no control character (8), and a handoff that is not found is said and write-handoff is never called (8, four shapes).
  • A prepared file the session never wrote is never sent (2), and the digest is of the bytes that are there (2).
  • Text rules (5 tests that no shell changes): the three variables are defined in a block, no line outside a comment pulls with --rebase, every block parses under bash -n, the refusal text keeps R-A11-11, and every register write and the handoff write names its lane.
  • Integration with the real lanes-edit.sh (10 tests, in slice 2a's Box: a bare origin, W, the lane's row and handoff, LANES_ROUTE_WRITES=1, a gh and a tmux that refuse):
    • the handoff is pushed from the lane's own checkout C with the Lane: trailer, and W is untouched: its HEAD, its status, its reflog, and a foreign entry STAGED in W stays staged and unpublished. Routed and through the managed seam, in both shells (4);
    • a peer edit between the digest and the call is exit 7, and nothing is published (2);
    • a foreign staged entry in C is exit 2, the file written and uncommitted (2);
    • a lane with no row says HANDOFF NOT FOUND, and nothing is cloned (2).

Not run here. The shell suite (tests/test_lane_helpers_suite.py) and the whole of tests/test_supervised_restart_regressions.py; CI's tests job is the suite of record. One static check stands in for the part of the shell suite that reads this skill: a script compared, at e2037b9 and at this head, every single-line string that tests/test_lane_helpers.sh asks has or hasnt of the skill (26 on $hfsk and 18 on $SWSK_TEXT). Every answer is the same at both heads, step 4's set-row-state "$lane" ... line included. No test other than the ones above pins the skill's bytes: tests/test_tests_workflow.py, tests/test_openrepotools_command.py and tests/test_install_skill_and_hook.py name the file but read it at run time, and the pyproject.toml entry is a path map. Those three were read, not run.

Mutants

15 one-line mutants of skills/handoff/SKILL.md, each applied to a git archive copy of b599ed9 and run against the module with -x, so each stops at the first test to fail. All 15 were killed.

mutant the test that killed it
m1: no --expect-digest test_block_calls_write_handoff_with_whole_and_expect_digest[bash]
m2: append-line without LANES_LANE test_step4_writes_carry_LANES_LANE[unset-bash]
m3: the workspace-root fallback back test_no_spelled_checkout_and_no_workspace_root_fallback[bash]
m4: no --create test_block_calls_write_handoff_with_whole_and_expect_digest[bash]
m5: no control-character tr test_the_subject_is_one_line_with_no_control_character[control-characters-removed-bash]
m6: exit 2 says pushed test_each_exit_of_the_verb_is_said_and_the_swap_goes_on[refused-after-writing-bash]
m7: write-handoff without LANES_LANE test_block_calls_write_handoff_with_whole_and_expect_digest[bash]
m8: set-row-state without LANES_LANE test_step4_writes_carry_LANES_LANE[unset-bash]
m9: a pull --rebase line test_no_spelled_checkout_and_no_workspace_root_fallback[bash]
m10: the prepared-file guard -s made -n test_a_prepared_file_the_session_never_wrote_is_never_sent[bash]
m11: exit 7 said as written test_each_exit_of_the_verb_is_said_and_the_swap_goes_on[moved-or-owned-bash]
m12: a placeholder subject test_block_calls_write_handoff_with_whole_and_expect_digest[bash]
m13: a cd to the checkout test_no_spelled_checkout_and_no_workspace_root_fallback[bash]
m14: publish-handoff back test_block_calls_write_handoff_with_whole_and_expect_digest[bash]
m15: no --whole test_block_calls_write_handoff_with_whole_and_expect_digest[bash]

Six of the 15 (m1, m4, m7, m12, m14 and m15) are killed by the one argv test, which is the test built to pin the call. Each mutant was killed in the bash arm, and the zsh arm reads the same text. No mutant of the integration tests was run.

Questions for lane openRepoTools-3

  1. handoff-path "$lane" --create, not the plan's bare call (F2). Step 2 reads the file for its digest and for the old bytes the new file keeps, so a bare handoff-path that names a path in a checkout nobody has cloned yet would leave nothing to read, for a lane whose first write this is. Is it right that a read at step 2 makes the lane's checkout, or should the skill make the bare call and say HANDOFF NOT FOUND where the file is not there?
  2. _NO_PULL_FILES is left for the merge of 2b (F7). The tuple line is one both slices edit. Is it right to add skills/handoff/SKILL.md to it as one word in 2b's merge, with this module's own regex test holding the rule until then?
  3. A lane checkout behind origin makes the first write-handoff --whole exit 7. The skill reads the digest from the file in the checkout, and the verb brings the checkout to origin only after that, under its lock. Where origin holds a newer handoff than the checkout does, the digest names bytes that the verb's own sync then replaces. The skill says what to do on 7 (read it again, make the new file from what is there, call once more), so no handoff is lost, but the first call of such a swap is wasted. The integration test of a peer's edit between the digest and the call shows the exit (7, nothing published, the block says to read it again); a checkout that is already behind gets it every time, not by a race, and no test makes that case on its own. Should handoff-path --create bring the checkout to origin when it makes it or finds it behind, so that the first call matches?
  4. Skill and helper skew at the cutover. A new skill run against an older lanes-edit.sh has no handoff-path: the helper answers "unknown subcommand", block 1 prints HANDOFF NOT FOUND and the swap goes on with no handoff refreshed. Distribution is openRepoTools --install, which writes the skill to ~/.claude-profiles/shared/skills/handoff/SKILL.md and ~/.claude/skills/handoff/SKILL.md, and opensoft/workBenches vendors a copy (named in skills/handoff/SKILL.md:310-315: the handoff commit is a bare git commit in a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197); none was touched or run here. Should the cutover put the helper (2a and 2b) in place before the skill, and should block 1 say "update lanes-edit.sh" where the helper does not know the verb?

Not changed

  • lane-start, lane-handoff and lane-end are as they were. Slice 2b makes the write-handoff and handoff-path calls of the first two, and slice 4 is lane-end's.
  • lanes-edit.sh and docs/README-lanes.md are slice 2a's and untouched here (the manual says only that the skill is the single source of the act).
  • tests/test_repo_hygiene.py's _NO_PULL_FILES (F7).
  • Steps 1, 3 and 5 of the skill, step 2's row read and top-block template, and step 4's log PAUSED write, which already carried LANES_LANE.
  • skills/lane-swap/SKILL.md, the alias, which restates no step.
  • The installed copies and the vendored one. Nobody ran openRepoTools --install or --uninstall, and nothing under ~/.claude or ~/.claude-profiles/shared was edited.

Refs #198

🤖 Generated with Claude Code

Summary by Sourcery

Delegate complete handoff publication to one lane-aware helper call and keep the handoff skill from managing checkouts or Git operations itself.

Bug Fixes:

  • Prevent the handoff skill from committing, pulling, or pushing through an incorrectly selected checkout, avoiding publication of unrelated staged changes.

Enhancements:

  • Move complete handoff publication into a single lane-aware write-handoff --whole operation with digest protection and explicit handling for write, commit, push, and concurrency outcomes.
  • Locate handoffs through the lane-aware helper without spelling checkout paths, preserve swap progress when handoff publication fails, and route register writes through the active lane.
  • Replace checkout-commit integration tests with coverage for the new single-call workflow, exit handling, subject construction, lane attribution, and workspace isolation.

Tests:

  • Add shell and integration coverage for lane-owned handoff publication, digest races, staged-entry protection, missing handoffs, and all helper exit statuses.

…gh `handoff-path` and writes it with ONE `write-handoff --whole` call, so it spells no checkout and commits, pulls and pushes nothing of its own, and step 4's `append-line` and `set-row-state` carry `LANES_LANE` (#198)

Lane: openreposhape-2 (openRepoShape-2)

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

sourcery-ai Bot commented Oct 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR changes the handoff skill from independently locating, committing, pulling, and pushing a checkout to preparing the full handoff and delegating one lane-scoped, digest-guarded write-handoff --whole operation, with extensive shell and real-helper tests covering routing, refusal handling, and workspace isolation.

Sequence diagram for the lane-scoped handoff write

sequenceDiagram
    participant Skill as HandoffSkill
    participant Helper as lanes-edit.sh
    participant LaneCheckout as LaneCheckout
    participant Origin as Origin

    Skill->>Helper: handoff-path lane --create
    Helper-->>Skill: handoff_file
    Skill->>Skill: Read prior_sha256 and prepare complete file
    Skill->>Helper: write-handoff lane --whole prepared_file --expect-digest prior_sha256 --subject hf_subject
    Helper->>LaneCheckout: Lock, sync, verify digest, atomically replace, commit
    LaneCheckout->>Origin: Push handoff commit
    Origin-->>LaneCheckout: Read back published handoff
    LaneCheckout-->>Helper: Exit status and write result
    Helper-->>Skill: stdout path/digest or refusal details
    Skill->>Skill: Report result and continue swap
Loading

Flow diagram for handoff refusal handling

flowchart TD
    A[Locate handoff with handoff-path --create] --> B{File and digest available?}
    B -- No --> C[Print HANDOFF NOT FOUND]
    C --> D[Continue swap without write-handoff]
    B -- Yes --> E[Prepare complete handoff file]
    E --> F{Prepared file is non-empty?}
    F -- No --> G[Print HANDOFF NOT WRITTEN]
    G --> D
    F -- Yes --> H[Call write-handoff once with --whole and --expect-digest]
    H --> I{Verb exit}
    I -- 0 --> J[Report handoff written]
    I -- 2 --> K[Report refusal; file may be written but uncommitted]
    I -- 3 --> L[Report pushed but not confirmed]
    I -- 6 --> M[Report commit kept on lane checkout]
    I -- 7 --> N[Report digest conflict or restart ownership]
    I -- Other --> O[Report not published]
    J --> D
    K --> D
    L --> D
    M --> D
    N --> D
    O --> D
Loading

File-Level Changes

Change Details Files
Delegates complete handoff publication to the lane-aware write-handoff --whole verb instead of resolving a checkout and running Git directly.
  • Derives the handoff path with handoff-path --create, hashes the existing bytes, and prepares the complete replacement file.
  • Makes exactly one lane-scoped write-handoff call with --whole, --expect-digest, and a generated subject.
  • Documents and reports all verb outcomes while allowing the swap to continue on missing files or publication refusals.
  • Removes checkout resolution, cd, commit, pull/rebase, push, and rollback logic from the skill.
skills/handoff/SKILL.md
Replaces checkout-and-Git integration tests with hermetic tests for the two new shell blocks and their lane-routing behavior.
  • Verifies exact helper arguments, digest and prepared-file contents, subject sanitization, single-call behavior, and all exit-code messages under Bash and Zsh.
  • Covers missing handoffs, empty prepared files, digest races, staged-entry refusals, lane-checkout publication, and preservation of the shared checkout.
  • Checks that register writes and the handoff write propagate LANES_LANE, and that the skill contains no checkout or rebasing-pull logic.
tests/test_handoff_skill_commit_block.py

Possibly linked issues

  • #unknown: The PR removes the faulty skill-managed commit, pull, and push block; write-handoff handles publication from the lane’s own checkout.

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

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant