Skip to content

fix: answer name-membership in-process, not through a pipe into grep -Fxq - #19

Merged
forkrul merged 1 commit into
masterfrom
claude/screen-warp-fonts-research-uksv3z
Sep 18, 2026
Merged

forkrul merged 1 commit into
masterfrom
claude/screen-warp-fonts-research-uksv3z

Conversation

@forkrul

@forkrul forkrul commented Sep 18, 2026

Copy link
Copy Markdown
Owner

The symptom

--verify intermittently condemned a healthy link as stale on macOS, failing the smoke job on master (run 29, 20d91bf, 2026-09-09) and again on #18's first commit:

install.sh: line 104: printf: write error: Broken pipe
[ERR] stale damascus-owned link: .../.claude/skills/bdd-tdd-execution
[ERR] 1 problem(s) found — re-run install.sh to repair
FAIL: --verify failed after repair

Not a flake. It reproduces on demand once the mechanism is understood.

The mechanism

Four call sites asked "is this name one we ship?" as a pipeline:

expected_skill_names | grep -Fxq "$name"          # prune(), --verify
printf '%s\n' "${AGENTS[@]}" | grep -Fxq "$name"  # prune(), --verify

grep -Fxq matches correctly and exits on the first hit. Every name emitted after that hit then writes into a closed pipe, and set -o pipefail (install.sh:16) promotes the producer's EPIPE to the pipeline's exit status. So a name damascus does ship reads as one it does not — and the link is pruned or flagged stale.

Two conditions gate it, which is exactly why it looked random:

  • the match must not be the last line emitted (nothing is written after a last-line match, so no EPIPE)
  • the producer must be slower than grep — on Linux the whole list lands in one read before grep exits, so it never trips; stock bash 3.2 on macOS is slow enough that it does

The victim, bdd-tdd-execution, is an alias that is not last in ALIASES.

Reproduced before fixing

Modelling "producer slower than consumer" makes it deterministic:

alpha    pipe+grep: MISSED*   loop: MATCH     <- first name
echo     pipe+grep: MISSED*   loop: MATCH     <- middle name
juliet   pipe+grep: MATCH     loop: MATCH     <- last name, nothing written after

A present name reading as MISSED through the pipe is the bug, in one line.

The fix

expected_skill_names (an emitter) becomes is_expected_skill (a predicate), plus is_expected_agent for the two AGENTS sites. Plain loops with an early return: no subshell, no pipe, no reader that can exit first.

bash 3.2 safe, and written as if ...; then return 0; fi rather than [ ... ] && return 0, which would be an errexit footgun under set -e.

Verification

  • All 25 shipped names accepted — 6 stages, 8 KEEP, 6 aliases, 5 agents — bdd-tdd-execution included; unknown names (retired-old-name, not-an-agent) still correctly rejected
  • tests/smoke.sh 10/10 consecutive runs, with zero Broken pipe anywhere in the output
  • Every other CI gate re-run locally: bash -n, alias symlinks, SKILL.md/agent frontmatter, submodule-tag + README table (superpowers @ v6.3.0, spec-kit @ v1.0.6)
  • Sourcing guard intact — CI can still source install.sh to read the manifests; verified by sourcing it and reading all four arrays

shellcheck isn't available in this environment, so that specific linter only runs in CI. The change removes two pipelines and adds no new external commands.

A note on the CHANGELOG

Recorded as a sharpened guarantee on the existing install.sh Added line — "prunes damascus-owned links whose names are no longer shipped and never one it still ships" — rather than as a Fixed entry. [Unreleased] states there is no earlier release to fix against, and this bug never shipped in one, so a Fixed line would describe a regression no reader ever saw. Easy to flip if you'd rather it be explicit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NQQrX3uYoeNyqxYkSbRp2W


Generated by Claude Code

…-Fxq

`--verify` intermittently condemned a healthy link as stale on macOS, failing
the smoke job on master (run 29, 20d91bf) and again on #18's first commit:

    install.sh: line 104: printf: write error: Broken pipe
    [ERR] stale damascus-owned link: .../.claude/skills/bdd-tdd-execution
    [ERR] 1 problem(s) found — re-run install.sh to repair
    FAIL: --verify failed after repair

Four call sites asked "is this name one we ship?" as `<emit names> | grep -Fxq`.
grep matches correctly and exits on the first hit — but every name emitted after
that hit then writes into a closed pipe, and `set -o pipefail` (line 16) promotes
that EPIPE to the pipeline's exit status. So a name damascus DOES ship reads as
one it does not, and the link is pruned or flagged.

Only a match that is not the last line emitted can trigger it, and only when the
producer is slower than grep — hence macOS-only and intermittent. The victim was
`bdd-tdd-execution`, an alias that is not last in ALIASES.

Reproduced deterministically by making the producer slower than the consumer: a
match on the first or a middle name reads as MISSED through the pipe and MATCH
through a loop; a match on the last name reads correctly through both, because
nothing is written after it.

`expected_skill_names` (emitter) becomes `is_expected_skill` (predicate), plus
`is_expected_agent` for the two AGENTS sites. Plain loops with an early return:
no subshell, no pipe, no reader that can exit first. bash 3.2 safe, and the
`if ...; then return 0; fi` form avoids the `&& return` errexit footgun.

Verified: all 25 shipped names (6 stages, 8 KEEP, 6 aliases, 5 agents) accepted,
including bdd-tdd-execution; unknown names still rejected; smoke suite 10/10
consecutive runs with zero "Broken pipe" in the output; sourcing guard intact so
CI can still read the manifests.

CHANGELOG records the guarantee on the existing install.sh Added line rather
than as a Fixed entry: [Unreleased] states there is no earlier release to fix
against, and this bug never shipped in one.

Feature: install-link-verification
Symbol: is_expected_skill,is_expected_agent,expected_skill_names,pipefail,SIGPIPE
Errors: write error: Broken pipe,stale damascus-owned link,--verify failed after repair

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQQrX3uYoeNyqxYkSbRp2W
@forkrul
forkrul merged commit 68ff547 into master Sep 18, 2026
4 checks passed
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.

2 participants