Skip to content

fix(vibin): stop worktree-setup scripts aborting with a silent SIGPIPE - #12

Merged
jmagar merged 1 commit into
mainfrom
fix/worktree-setup-sigpipe-20260806
Aug 7, 2026
Merged

fix(vibin): stop worktree-setup scripts aborting with a silent SIGPIPE#12
jmagar merged 1 commit into
mainfrom
fix/worktree-setup-sigpipe-20260806

Conversation

@jmagar

@jmagar jmagar commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

The failure

worktree-new.sh, worktree-sync.sh, and worktree-rm.sh exit 141 with no
output at all
— no error, no partial work, nothing to diagnose. Measured in
~/workspace/labby (35 worktrees):

installed (unfixed) script: 7/8 aborted
fixed script:               0/8 aborted

Root cause

Every script resolved the repo root the same way:

ROOT=$(git worktree list --porcelain | awk '/^worktree /{print $2; exit}')

awk's exit closes the pipe after the first record while git is still writing.
git dies of SIGPIPE, set -o pipefail promotes 141 to the pipeline status, and
set -e aborts the script — even though the assignment already succeeded.

It is invisible in a small repo: git emits short porcelain output in one
write() and never observes the closed reader. The crash needs the stream to
exceed git's stdio buffer so a second write() happens. It is also a race,
not a certainty, so a single-shot test misses it.

The fix

All four sites now match once without exiting, so awk drains git's output:

awk '/^worktree /{if(!s){print $2; s=1}}'

The one remaining early-close (git lfs ls-files | head -1) is safe because it
sits inside [[ ]], where a command substitution's status cannot reach
pipefail. That is now commented so a refactor out of [[ ]] doesn't silently
reintroduce the crash.

Tests

New smoke-test section builds a 25-worktree fixture with long branch names
(8362 B of porcelain — asserted to exceed the buffer, since a smaller fixture
proves nothing) and runs the real scripts in a loop. Against the pre-fix
scripts it catches the abort 4/20 and 7/8 of the time.

tests/smoke.sh — all pass, including the two new checks.

`worktree-new.sh`, `worktree-sync.sh`, and `worktree-rm.sh` could exit 141
with no output at all, leaving the caller with a bare failure and nothing to
diagnose. In `~/workspace/labby` (35 worktrees) `worktree-sync.sh --check`
aborted 7 times out of 8.

Every one of them resolved the repo root with

    ROOT=$(git worktree list --porcelain | awk '/^worktree /{print $2; exit}')

The awk `exit` closes the pipe after the first record while git is still
writing. git dies of SIGPIPE, `set -o pipefail` promotes 141 to the pipeline
status, and `set -e` aborts — even though the assignment already succeeded.

It is invisible in a small repo: git emits short porcelain output in a single
write() and never observes the closed reader. The failure needs the stream to
exceed git's stdio buffer so a second write() happens, which is why this only
ever showed up in busy checkouts. It is also a race, not a certainty, so a
one-shot test misses it.

Fixed all four sites to match once without exiting, so awk drains git's
output. The one remaining early-close (`git lfs ls-files | head -1`) is safe
because it sits inside `[[ ]]`, where a command substitution's status cannot
reach pipefail; that is now commented so a future refactor out of `[[ ]]`
does not silently reintroduce the crash.

Added a smoke-test section that builds a 25-worktree fixture with long branch
names (8362 B of porcelain, comfortably past the buffer) and runs the real
scripts in a loop. Against the pre-fix scripts it catches the abort 4/20 and
7/8 of the time; a single invocation would not have.
@jmagar
jmagar merged commit d86f1f0 into main Aug 7, 2026
10 checks passed
@jmagar
jmagar deleted the fix/worktree-setup-sigpipe-20260806 branch August 7, 2026 03:15
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