Skip to content

lane-handoff reads the lane lifecycle through a pipeline whose awk exits early, so under pipefail the read dies of SIGPIPE (exit 141) now and then and the handoff records no transition #280

Closed

Description

@brettheap

What happens

lane-handoff reads the lane's lifecycle through a pipeline that can die of SIGPIPE.

  • The pipeline: lane-handoff:1490-1491 and :1505-1506 at 670a852 pipe lanes-edit.sh lane-state "$lane" into awk -F"\t" '$1 == "state" { print $2; exit }', and the second one into the same filter for operation.
  • The race: the script runs under set -uo pipefail (lane-handoff:120). awk exits as soon as it has printed its one field. If lanes-edit.sh writes again after that, the write gets SIGPIPE, lanes-edit.sh dies with 141, and under pipefail the whole substitution's status is 141.
  • What the handoff does then: case "$lcb_rc" in 0 | 8) takes 141 as a failed read. The handoff prints "the lane lifecycle could not be read (… lane-state … exited 141), so this handoff records no RUNNING -> SWAPPING -> SWAPPED transition", and returns from the lifecycle block. It records no fence, takes no inventory, and makes no transition.
  • It fails safe: the swap still goes on, and the lane is read later as a lane with no recorded swap.
  • But it is wrong, and it depends on timing alone. The read did not fail: the reader stopped listening.

How it was found

Where else

The same shape occurs elsewhere: a command whose output is read by a consumer that stops early (awk … exit, head -1), under pipefail. At 670a852, by grep:

  • lane-handoff: 32 | awk … exit } pipelines and 2 | head -1;
  • lanes-edit.sh: 18 and 27;
  • lane-start: 19 and 1;
  • lane-end: 0 and 1.

Only the ones whose producer writes in several pieces can race. A producer that writes its whole output in one write finishes before the reader exits.

The fix

  • For the lifecycle reads, read the whole output first, then parse it: lcb_raw="$(… lane-state "$lane" 2>/dev/null)" || lcb_rc=$?, then awk on printf '%s\n' "$lcb_raw". The read's status is then the producer's own, and nothing can end the producer early.
  • For the other sites, audit each pipeline fed by a command. Do the same where the producer can write after the consumer exits, and leave the rest.
  • Test it deterministically: a lane-state that prints the state line, then writes again after a short pause, makes the old pipeline exit 141 every time.

Why now

Every PR's tests job runs the T018 test, so the race can turn any PR's CI red at random. It can also flake the candidate suites of the triad migration's rehearsal (#186). Lane openRepoTools-3, which owns the lane tooling, is fixing it now, to land before the rehearsal's freeze.

Lane: openRepoTools-3

🤖 Generated with Claude Code

Activity

  1. added this to the v1.1.0 milestone on Oct 19, 2026
  2. brettheap commented on Oct 19, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoTools-3, session 83619980-06bb-4469-ae73-2a8c5e61981a@Eagle, 2026-10-19T22:42:17Z, for #280

    Logged in lanes/log/openRepoTools-3.md at 28b38c12debd47140496fb16081dcf979d53736c.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoTools#280:
       none
    2. `gh pr list --repo opensoft/openRepoTools --state all --search "#280"`, kept where the row or its body names `#280`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoTools` branches naming `280` as a whole token:
       none
    
  3. brettheap commented on Oct 20, 2026

    @brettheap
    ContributorAuthor

    Lane openRepoTools-3, 2026-10-20T00:5xZ: a correction to this issue, the fix's state, and a follow-up.

    Lane: openRepoTools-3

  4. added a commit that references this issue on Oct 20, 2026
    677acc0
  5. brettheap commented on Oct 20, 2026

    @brettheap
    ContributorAuthor

    RELEASED — lane openRepoTools-3, session 83619980-06bb-4469-ae73-2a8c5e61981a@Eagle, 2026-10-20T03:44:03Z, for #280 — #283 landed as 677acc0 and closed #280

  6. added 3 commits that reference this issue on Oct 21, 2026
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

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions