Skip to content

Handoff skill: resolve lanes-edit.sh on PATH first, correct the comments that still promised write (c) without a workstation, and refuse the object-log and register-line writes for a lane that has never had a session recorded - #207

Merged
brettheap merged 6 commits into
mainfrom
fix/handoff-skill-98-99-100
Oct 19, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What was wrong

Three findings from opensoft/workBenches#93's audit of skills/handoff/SKILL.md, all pre-existing:

What changed (skills/handoff/SKILL.md, line numbers in the new file)

lines section change
52-53, 59 preamble and step 1's first block #100: $L is command -v lanes-edit.sh, else the symlink, byte for byte the line /restart has; the sentence introducing $L says so
127 step 1, workstation comment #99: step 4 skips (a), (b) and (c) alike
237-266 step 2, first block #98: the session's own id comes first ($CLAUDE_CODE_SESSION_ID, shape-checked; the comment says only that, as the launcher's window exports and $CLAUDE_PROFILE_NAME are in step 4), the row's last uuid second, as lane-handoff takes them. uuid_missing is set only where neither answers and the row was read (a failed register-row read is no answer, Amendment 7(d)). The NO SESSION RECORDED message prints the act filled in from $lane (lane-start --no-launch <repo> <n>, Amendment 6(c)'s session-cell append) and says whether the row's cell will be set (it is not when $ws_missing is also set). The command is split from $lane by parameter expansion (${lane%-*}, ${lane##*-}) behind [[ "$lane" =~ ^.+-[0-9]+$ ]], as step 1 does, because zsh, the shell a session's Bash tool runs in, fills $match and not BASH_REMATCH. Nothing is written here.
273-274 step 2, prose says where $uuid comes from
332-339 step 2, prose #98: a paragraph telling the handoff to name the gap (session unknown on its first line, as lane-handoff writes it)
548-550 step 4, payload comment #99: the comment names (a), (b) and (c) as the writes a missing workstation stops
577-587, 595, 602-603, 606 step 4, (a) and (b) #98: a comment and an inner if [[ -z "$uuid_missing" ]] around (a) and (b); (b)'s comment carries the exception. The outer if [[ -z "$ws_missing" ]] line is untouched.

Where neither id answers (a plain shell outside any session, on a row recording none; the rare case R-A11-5 describes), step 4 skips the object-log PAUSED and the register's file-level line and still sets the row's state cell. lane-handoff skips (b) the same way (lane-handoff:2293-2297) but still attempts (a) and prints the writer's refusal (:2278); the skill is told first instead, which is the guard R-A11-5 asks for.

Question for Brett

For that rare case R-A11-5's wording (Amendment 11, :972-976: "the swap writes what it legally can: the register's file-level PAUSED line and the row's state cell") reads as the file-level line (b) being written. This PR skips it, as lane-handoff does. append-line itself checks no session field, so skipping is a choice and not something the writer forces; R-A11-15 (:995-997) says R-A11-11's rule that (b) and (c) survive the uuid refusal is "never a licence to invent a value for a field", which a third reading takes as: write (b) with the session clause omitted. Which do you want, skip (b) as now, or write (b) with no session clause?

Neighbours

#199 (#197) has landed (83d2690), with #201, #212, #215 and #216, and main is now f595e44. I merged this branch against the heads of main (f595e44) and of #200 (f12abc3) with git merge-tree: no conflicts with either, and every shell block in the merged result of #200 passes bash -n. The block that fails bash -n on main (block 11, with a <profile> placeholder) is #200's to fix.

Tests

New tests/test_handoff_skill_98_99_100.py (29 cases). It extracts the skill's fenced blocks and runs them under bash against a stub lanes-edit.sh that records every call, with a stub tmux, a minimal environment and a temporary $HOME:

  • skills/handoff/SKILL.md:39 hard-codes lanes-edit.sh's path; every sibling artifact (including skills/restart/SKILL.md) resolves it PATH-first #100: the L= line equals the restart skill's; with the helper on PATH it wins, without it the symlink path is the fallback; the introducing sentence says PATH first.
  • skills/handoff/SKILL.md:474 still says '(c) below still runs', the exact promise R-A11-27 refuses, uncorrected 60 lines above the fix #99: no line promises (c) runs without a workstation (the surviving quotation of the old promise says "promised"); the comments name (a), (b) and (c); step 4 run with ws_missing=1 makes none of the three writes.
  • skills/handoff/SKILL.md: the skill supplies the uuid (R-A11-5) but has no preemptive refusal for a lane that has never had a session recorded #98: step 2 and step 4 are run in one shell. With CLAUDE_CODE_SESSION_ID set and a row recording none, nothing is flagged and log and append-line are attempted with that id; the session's id beats the row's last one; a malformed id is ignored; with neither id, log and append-line are skipped, set-row-state is still called with transcript none, and session @ is never written; a failed register-row read (exit 3) prints no "never had a session" message; the message and step 4 agree on whether the row's cell is set with and without a workstation; the command is filled in from the lane, and left as <repo> <n> for a name that is not <repo>-<n>; the handoff paragraph says session unknown.
  • zsh: step 2 is run under bash and under zsh (zsh -f -c) in five cases (no session, no session with a lane name that is not <repo>-<n>, the session's own id, the row's last id, a failed read) and the two outputs must be identical; the first also names lane-start --no-launch repoHS 17. It runs only where zsh is installed, so it does not run on CI. The Linux image has no zsh: the tests-no-submodule job of run 39069499556, which lists its skips, printed SKIPPED [5] … zsh is not installed for it, and the tests job runs on the same image. It does run on the workstations, where zsh is the login shell and the shell a session's Bash tool runs in, which is where the defect shows. Installing zsh in the Linux tests job would change that workflow's rule that pytest is the only thing it installs (ci: run the test suite on Linux only #227), so that is left to Brett and is not done here. The restart command had been filled from BASH_REMATCH, which zsh does not set, so under zsh it printed both fields empty and a bash-only test missed it.

Checked by running each test function by hand: all 29 pass on this head. The zsh case no-session is red at bb3a23f (zsh and bash disagree: zsh printed the command with both fields empty; the other zsh cases there are controls), five of the eight cases added in the second round and the strengthened handoff-paragraph case are red at 00814c0, and the first round's 10 are red on main.

Under the shared lock (flock -w 7200 ${TMPDIR:-/tmp}/openrepotools-pytest.lock python3 -m pytest … -q -p no:cacheprovider, one hold for both): tests/test_handoff_skill_98_99_100.py on 5675b70 gave 29 passed in 6.70s; the same file with -k zsh against a git archive of bb3a23f gave 1 failed, 4 passed, 24 deselected in 5.08s, the failure being test_step_2_says_the_same_under_zsh_as_under_bash[no-session…repoHS-17].

CI run 39069499556 on 5675b70 is green: tests, tests-no-submodule and guard-launch-mode (which holds the repository hygiene checks) all success. Runs 38448681350 (00814c0) and 38459000363 (bb3a23f) were green.

Closes #98
Closes #99
Closes #100

🤖 Generated with Claude Code

Summary by Sourcery

Harden the handoff skill against missing session and workstation context while aligning helper resolution, guidance, and tests with the supported behavior.

Bug Fixes:

  • Prevent handoff writes for lanes without a valid recorded or current session UUID, while still updating the lane state when a workstation is available.
  • Ensure handoff guidance names the missing session and the command needed to record one.

Enhancements:

  • Resolve lanes-edit.sh from PATH before falling back to the existing symlink.
  • Correct handoff comments and behavior so missing workstations suppress all register and log writes.

Tests:

  • Add coverage for helper resolution, missing-workstation behavior, missing-session handling, UUID precedence and validation, failed reads, lane command generation, and Bash/zsh output consistency.

brettheap and others added 2 commits October 11, 2026 16:55
…blocks against a stub helper: #98's missing session, #99's stale promise about write (c), #100's hard-coded helper path

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e comments that still promised write (c) without a workstation (#99), and refuse the object-log and register-line writes for a lane that has never had a session recorded, naming the gap (#98)

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

sourcery-ai Bot commented Oct 13, 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 23 hours and 41 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates the handoff skill to use the same PATH-first helper resolution as restart, corrects misleading write-related comments, and fail-closed guards session-keyed writes when a lane has no recorded session UUID; a hermetic test suite verifies the prose, command resolution, and write behavior.

Sequence diagram for session-guarded handoff writes

sequenceDiagram
    participant Skill as HandoffSkill
    participant Shell
    participant lanes_edit_sh as lanes-edit.sh
    participant Operator

    Skill->>Shell: command -v lanes-edit.sh
    Shell-->>Skill: PATH helper or fallback path
    Skill->>Skill: Read session uuid from lane row
    alt uuid is recorded
        Skill->>lanes_edit_sh: log PAUSED
        Skill->>lanes_edit_sh: append-line PAUSED
    else uuid is missing
        Skill->>Operator: Report NO SESSION RECORDED
        Skill-->>Skill: Skip log PAUSED and append-line
        Skill->>Operator: Print lane-start --no-launch <repo> <n>
    end
    Skill->>Skill: Refresh handoff and write row state
Loading

File-Level Changes

Change Details Files
Make the handoff skill resolve its helper consistently and enforce session/workstation write guards.
  • Resolve lanes-edit.sh from PATH before using the legacy symlink fallback.
  • Update comments and prose to state that missing workstation context suppresses all three writes.
  • Detect rows without a recorded transcript UUID, explain the gap and recovery command, and skip session-keyed object-log/register writes while retaining the row-state update.
skills/handoff/SKILL.md
Add hermetic behavioral and textual tests for the handoff precision fixes.
  • Verify PATH-first helper resolution matches the restart skill.
  • Exercise write behavior with valid, missing-workstation, and missing-session inputs.
  • Check that comments and handoff instructions accurately describe suppressed writes and session recovery.
tests/test_handoff_skill_98_99_100.py

Assessment against linked issues

Issue Objective Addressed Explanation
#96 Revise lane-end’s top-level USAGE synopsis so it accurately distinguishes retiring a FORK, which writes and kills nothing, from retiring a DUPLICATE HOLDER, which TERMs the background holder pair. ❌ The provided diff contains no changes to lane-end or its USAGE synopsis. It changes the handoff skill and adds tests for those changes, so the inaccurate synopsis remains.
#108 Update the canonical TAKEOVER grammar table in docs/README-lanes.md to include TAKEOVER ← lane:<dispossessed lane> alongside the stale-claim-URL form. ❌ The PR description claims this documentation change, but the supplied diff contains no change to docs/README-lanes.md. The shown changes address unrelated skill, workflow-comment, and lane-end 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

…e with no session only when neither answers and the row was read, and make the message and the handoff say only what step 4 will do

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…er expansion, which zsh runs as bash does, and say only what the shape checks cover; run step 2 under zsh in the tests

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

Copy link
Copy Markdown

@brettheap
brettheap merged commit 235c68a into main Oct 19, 2026
5 checks passed
@brettheap
brettheap deleted the fix/handoff-skill-98-99-100 branch October 19, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready landing gate: runs tests-macos once before the squash

Projects

None yet

1 participant