Summary
tests/validate-content.sh aborts with exit 128 and prints no === Summary === block when run outside a tagged git checkout (e.g. from the installed plugin cache, a shallow CI clone without fetch-tags, or a fork before its first v2.* tag). The companion tests/validate-structure.sh handles the same no-git situation gracefully, so this is an inconsistency in one of the two shipped self-test validators.
Discovered during a QA pass on the v2.18.3 release.
Root cause
tests/validate-content.sh:654:
all_tags=$(git tag -l "v2.*" 2>/dev/null | sort -V)
The script runs under set -euo pipefail. In a non-git directory git tag -l exits 128; 2>/dev/null suppresses the stderr but not the exit code, and pipefail propagates 128 out of the pipeline. The command substitution then carries that status into the assignment, and errexit aborts the script immediately — before the intended fallback at lines 655–656 can run:
all_tags=$(git tag -l "v2.*" 2>/dev/null | sort -V)
if [ -z "$all_tags" ]; then
warn "git tag history unavailable — skipping SELF-CHECK.md body freshness ..."
The inline comment at line 653 documents the intended behavior the code doesn't deliver:
# Dev-env: no tags → WARN+skip (CI sets fetch-tags: true so drift still caught at merge).
The sibling tests/validate-structure.sh already guards every git call correctly, e.g.:
# validate-structure.sh:797 / :803
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
CHANGED=$(git diff --name-only "$DIFF_BASE"..HEAD 2>/dev/null || echo "")
validate-content.sh:654 is the only git call missing that || echo "" guard.
Reproduce
$ cp -R <installed 2.18.3 tree> /tmp/nb283 # any non-git dir
$ cd /tmp/nb283 && bash tests/validate-content.sh; echo "exit=$?"
...
PASS: docs/wiki/Changelog.md badge matches v2.18.3
exit=128 # <-- no "=== Summary ===", no RESULT line
Isolated mechanism:
$ bash -c 'set -euo pipefail; x=$(git tag -l "v2.*" 2>/dev/null | sort -V); echo "REACHED"'; echo $?
128 # "REACHED" never prints — aborts at the assignment
tests/validate-structure.sh in the same directory completes normally (358 PASS / 0 FAIL, Check 27 cleanly reports "no release tag yet — Check 27 skipped").
Impact
- The release notes' "269 PASS / 0 FAIL / 1 WARN (content)" is only reproducible inside the maintainer's tagged git checkout. The shipped artifact, run standalone, yields
exit 128 with no summary — which reads as a crash, not a clean skip.
- Affects any consumer running the shipped self-test from the installed plugin, plus shallow CI clones without
fetch-tags: true and forks before their first v2.* tag.
- H1 (Be Proactive) lens: for a plugin whose thesis is verification discipline, one of its two verification surfaces isn't proactive about the no-git case the other one already handles.
Proposed fix (illustration — not a PR)
One line, matching the guard validate-structure.sh already uses:
--- a/tests/validate-content.sh
+++ b/tests/validate-content.sh
@@ -651,7 +651,7 @@
- all_tags=$(git tag -l "v2.*" 2>/dev/null | sort -V)
+ all_tags=$(git tag -l "v2.*" 2>/dev/null | sort -V || echo "")
Verified: with this guard, the same non-git run completes exit=0 →
=== Summary === PASS: 267 / FAIL: 0 / WARN: 2 / ALL CHECKS PASSED
(the 2nd WARN is the intended "git tag history unavailable — skipping sub-checks E + F"). Inside a tagged checkout, behavior is unchanged: E+F run and the figure is the documented 269 PASS / 1 WARN.
Secondary note (LOW / by-design question)
The new guides/anthropic-engineering-doctrine-audit.md Table 1 cites 3 maintainer-local ~/.claude/lessons/... paths (the auto-commit-hook lesson, the notebook-agent-harness lesson, and INDEX.md) that aren't shipped with the plugin, so 3 of the 12 "verify before re-proposing" rows can't be checked by a consumer. The guide self-describes as a contributor/maintainer defensive-citation surface, so this may be intentional — flagging in case a one-line "lesson citations are maintainer-local" disclaimer (or relativized paths) would help consumers reading Table 1.
QA context: v2.18.3, commit f0fa240. Structure validator and all release-notes content/version claims otherwise verified correct.
Summary
tests/validate-content.shaborts withexit 128and prints no=== Summary ===block when run outside a tagged git checkout (e.g. from the installed plugin cache, a shallow CI clone withoutfetch-tags, or a fork before its firstv2.*tag). The companiontests/validate-structure.shhandles the same no-git situation gracefully, so this is an inconsistency in one of the two shipped self-test validators.Discovered during a QA pass on the v2.18.3 release.
Root cause
tests/validate-content.sh:654:all_tags=$(git tag -l "v2.*" 2>/dev/null | sort -V)The script runs under
set -euo pipefail. In a non-git directorygit tag -lexits128;2>/dev/nullsuppresses the stderr but not the exit code, andpipefailpropagates128out of the pipeline. The command substitution then carries that status into the assignment, anderrexitaborts the script immediately — before the intended fallback at lines 655–656 can run:The inline comment at line 653 documents the intended behavior the code doesn't deliver:
The sibling
tests/validate-structure.shalready guards every git call correctly, e.g.:validate-content.sh:654is the only git call missing that|| echo ""guard.Reproduce
Isolated mechanism:
tests/validate-structure.shin the same directory completes normally (358 PASS / 0 FAIL, Check 27 cleanly reports "no release tag yet — Check 27 skipped").Impact
exit 128with no summary — which reads as a crash, not a clean skip.fetch-tags: trueand forks before their firstv2.*tag.Proposed fix (illustration — not a PR)
One line, matching the guard
validate-structure.shalready uses:Verified: with this guard, the same non-git run completes
exit=0→=== Summary === PASS: 267 / FAIL: 0 / WARN: 2 / ALL CHECKS PASSED(the 2nd WARN is the intended "git tag history unavailable — skipping sub-checks E + F"). Inside a tagged checkout, behavior is unchanged: E+F run and the figure is the documented 269 PASS / 1 WARN.
Secondary note (LOW / by-design question)
The new
guides/anthropic-engineering-doctrine-audit.mdTable 1 cites 3 maintainer-local~/.claude/lessons/...paths (the auto-commit-hook lesson, the notebook-agent-harness lesson, andINDEX.md) that aren't shipped with the plugin, so 3 of the 12 "verify before re-proposing" rows can't be checked by a consumer. The guide self-describes as a contributor/maintainer defensive-citation surface, so this may be intentional — flagging in case a one-line "lesson citations are maintainer-local" disclaimer (or relativized paths) would help consumers reading Table 1.QA context: v2.18.3, commit
f0fa240. Structure validator and all release-notes content/version claims otherwise verified correct.