Skip to content

lane-handoff takes a CLOSED lane to SWAPPING, reopening a terminal lane #118

Description

@brettheap

lane-handoff's lifecycle_begin reads the lane's current state and hands it straight back as the compare-and-swap's expected value:

	lcb_now="$(LANES_NO_FETCH=1 "$LANES_EDIT" lane-state "$lane" 2>/dev/null \
		| awk -F"\t" '$1 == "state" { print $2; exit }')" || lcb_rc=$?
	...
	[ -n "$lcb_now" ] || lcb_now=none
	...
	lcb_out="$(LANES_NO_FETCH=1 "$LANES_EDIT" set-lane-state "$lane" SWAPPING \
		--expect "$lcb_now" --owner "${uuid:-none}" ...)"

--expect asks has the lane moved since I read it and nothing else, so every state is a legal source for SWAPPING — CLOSED included. A handoff run on a lane whose lifecycle says CLOSED takes it to SWAPPING, polls it, records an inventory under that operation, appends a PAUSED record and finishes at SWAPPED: a terminal lane reopened, with a lifecycle that now says a swap is what last happened to it.

CLOSED is written by lane_state_follow for an ENDED or a RETIRED line — the two verbs that end a lane — and the manual's own table reads it as the lane is finished. The lifecycle has no transition classes at all: which word may follow which is expressed only in what each caller happens to pass.

The reachable shape is not exotic: lane-end closes a lane, and a /handoff typed (or a skill run) in that pane afterwards is an ordinary mistake — the pane, the window name and the register row are all still there.

Why this is filed rather than fixed on #97

Copilot review rounds are capped at two per pull request (Brett Heap's ruling of 2026-09-16); #97 is at round six and took only the two safety holes it could not land with. And the fix here is a DESIGN decision rather than a guard: R-A11-11 is that a swap is never left unwritten, and the lifecycle on that branch is bound by it — a control root that cannot be derived, a fence another act moved, a snapshot that cannot be written: each of them costs the LIFECYCLE and never the record. A handoff that REFUSES on a closed lane would be the first time the lifecycle stopped the swap, and a handoff that silently declines the transition would leave a lane whose record says PAUSED and whose snapshot says CLOSED. Which of those is right is Brett's to rule.

What's open

Name the legal sources of every transition (a transition table, rather than each caller's --expect), and decide what a handoff on a CLOSED lane does: refuse before any handoff work, take an explicit recovery/reopen path that is visible as one, or record the swap and leave the lifecycle closed with the divergence reported by lane-reconcile.

Filed from Copilot round 6 on #97 — #97 (comment) — and named in openspec/changes/add-crash-consistent-lane-worktree-recovery/tasks.md section 7.

Activity

  1. brettheap commented on Sep 16, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoTools-3, session 4135b2c9-34a6-47e3-a156-5adebb632fae@Eagle, 2026-09-16T13:06:14Z, for #118

    Logged in lanes/log/openRepoTools-3.md at 9c4a6beca788ddb415eb8ce83a054d00ed32955d.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoTools#118:
       none
    2. `gh pr list --repo opensoft/openRepoTools --state all --search "#118"`, kept where the row or its body names `#118`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoTools` branches naming `118` as a whole token:
       none
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions