Two of the five required contexts in docs/BRANCH-PROTECTION.md print a verdict about something they never examined. Found by a read-only fan-out (29 agents, 41 candidates, 9 refuted), then reproduced by hand.
1. NOW Sync Gate — a revision it cannot resolve reads as a missing entry
scripts/ci/now-sync-gate-diff.sh:39
BASE="${PR_BASE_SHA:?}"
ADDED=$(git diff --diff-filter=A --name-only "$BASE" "$HEAD" | grep -E "$ENTRY_RE" || true)
${...:?} catches unset and empty. It does not catch a non-empty SHA naming an object this clone does not have — a shallow fetch, a force-push that orphaned the base, a rerun after the branch moved. git diff exits 128, and the || true that exists to absorb grep's no-match absorbs that identically.
Reproduced in a clean checkout:
$ GITHUB_EVENT_NAME=pull_request PR_BASE_SHA=0000000000000000000000000000000000000001 \
PR_HEAD_SHA=$(git rev-parse HEAD) bash scripts/ci/now-sync-gate-diff.sh
fatal: bad object 0000000000000000000000000000000000000001
::error::SYNC REQUIRED: this PR/push adds no docs/now/ entry.
...
EXIT=1
The contributor is told to write a file they may already have written. The push arm has the same shape with PUSH_BEFORE/PUSH_AFTER.
2. Issue Gate — no pull request reads as an unreferenced pull request
.github/workflows/issue-gate.yml:44
workflow_dispatch (line 14, added 2026-08-29 in 40d616925) carries no pull_request object, so PR_TITLE and PR_BODY render as empty strings, grep matches nothing, and the step prints
::error::L1 TRACEABILITY violation: No issue reference found in PR title/body.
against a pull request that does not exist, exiting 1 under the check-run context check-linked-issue. IS_BOT does not rescue it: a human dispatcher makes every disjunct false.
Not hypothetical — tools/check_now_entry_shape.py:185 already records the dispatch run that produced it: "it took a reading, and the reading said this gate goes red when fired outside its subject."
Proposed
Related, from the same sweep
13 more confirmed sites at silently-passes or noisy-but-harmless, including a fourth quiet shape that tri gates quiet (#3049) does not classify: if grep -n PATTERN A B 2>/dev/null; then … else echo OK, where grep's exit 2 (cannot open) merges with exit 1 (no match) and the else branch prints success. coq-kernel.yml:121 is the only instance in 49 workflow files; the reproduction ran GNU grep 3.11 with one operand absent and got OK: no Admitted, exit 0. Filed separately if wanted — this issue is the two that block merges.
Two of the five required contexts in
docs/BRANCH-PROTECTION.mdprint a verdict about something they never examined. Found by a read-only fan-out (29 agents, 41 candidates, 9 refuted), then reproduced by hand.1. NOW Sync Gate — a revision it cannot resolve reads as a missing entry
scripts/ci/now-sync-gate-diff.sh:39${...:?}catches unset and empty. It does not catch a non-empty SHA naming an object this clone does not have — a shallow fetch, a force-push that orphaned the base, a rerun after the branch moved.git diffexits 128, and the|| truethat exists to absorb grep's no-match absorbs that identically.Reproduced in a clean checkout:
The contributor is told to write a file they may already have written. The
pusharm has the same shape withPUSH_BEFORE/PUSH_AFTER.2. Issue Gate — no pull request reads as an unreferenced pull request
.github/workflows/issue-gate.yml:44workflow_dispatch(line 14, added 2026-08-29 in40d616925) carries nopull_requestobject, soPR_TITLEandPR_BODYrender as empty strings, grep matches nothing, and the step printsagainst a pull request that does not exist, exiting 1 under the check-run context
check-linked-issue.IS_BOTdoes not rescue it: a human dispatcher makes every disjunct false.Not hypothetical —
tools/check_now_entry_shape.py:185already records the dispatch run that produced it: "it took a reading, and the reading said this gate goes red when fired outside its subject."Proposed
git cat-file -e <sha>^{commit}before diffing. If either is absent, say the gate could not run, name the variable and the value, and exit 2 — the codescripts/triuses for an unbuilt compiler (pre-commit refuses the commit when t27c is not built, and says 'local commands still work' #3045) andt27c corpusfor a spec tree with no specs (A full disk is a second unlabelled channel in Outcome, and it reads as a compiler rejection #3025). The real verdict is unchanged for resolvable revisions.PR_NUMBER: ${{ github.event.pull_request.number }}and, when it is empty, print that there is no pull request to read and exit 0. POSIX 1003.3 calls that UNTESTED — no subject, which is different from an instrument that failed — and it is not a pass for any PR, which the message says.Related, from the same sweep
13 more confirmed sites at
silently-passesornoisy-but-harmless, including a fourth quiet shape thattri gates quiet(#3049) does not classify:if grep -n PATTERN A B 2>/dev/null; then … else echo OK, where grep's exit 2 (cannot open) merges with exit 1 (no match) and theelsebranch prints success.coq-kernel.yml:121is the only instance in 49 workflow files; the reproduction ran GNU grep 3.11 with one operand absent and gotOK: no Admitted, exit 0. Filed separately if wanted — this issue is the two that block merges.