Refs #416 (structural follow-up from that session; not fixes).
Why
The #416 session shipped a PR whose committed tree lacked a fix that lived only in the working tree: a git reset --soft + git add committed a stale index, so the push, CI, and a cold reviewer each saw a different tree — the reviewer "confirmed" a --no-renames fix that hadn't been committed. Local build/test/review operate on the working tree; what ships is the committed tree, and nothing enforced that they match. This is the mechanized half of that lesson (the review-process half — point reviewers at git show <sha>:<file> — stays guidance).
What
New fail-open pre-push hook .claude/hooks/prepush-clean-worktree-check.sh, wired into .husky/pre-push after the H11 rebase check (and before the slow web CI-parity build, so it fails fast). It blocks a push when a file in the branch's diff vs origin/main also has uncommitted working-tree or index changes — i.e. the pushed commit wouldn't match what you built/reviewed.
Precise scope (low false-positive): only files in the pushed diff; unrelated uncommitted scratch and untracked files never block.
Fail-open on anything undecidable (not a repo, offline, no origin/main); deliberate escape ETV_ALLOW_DIRTY_PUSH=1.
Same hook family / rationale as H11 (rebase-not-merge) and H12 (issue-qualification): "make the process rule a hook, not prose to remember" (#303).
Verification
Local: shellcheck clean; 4-case matrix — dirty PR-file → block; clean → allow; dirty non-PR file → allow; escape hatch → allow. Dogfooded: this very push ran through the new hook (clean tree → allowed).
Cold review (review-only agent): MERGEABLE, no blocker, no false-allow. One MEDIUM noted — pushing already-committed work while deliberately keeping uncommitted WIP in the same file is blocked; that's by-design (the escape hatch covers it), and for our commit-then-push worktree flow it's rare. A warning instead of a block would just be ignored, so the block stays.
Docs
docs/decisions.md entry (folds into docs/decisions/release-ci-governance.md alongside H11/H12 at the next release consolidation).
Refs #416 (structural follow-up from that session; not `fixes`).
## Why
The #416 session shipped a PR whose committed tree lacked a fix that lived only in the **working tree**: a `git reset --soft` + `git add` committed a stale index, so the push, CI, and a cold reviewer each saw a *different* tree — the reviewer "confirmed" a `--no-renames` fix that hadn't been committed. Local build/test/review operate on the working tree; what ships is the *committed* tree, and nothing enforced that they match. This is the mechanized half of that lesson (the review-process half — point reviewers at `git show <sha>:<file>` — stays guidance).
## What
New fail-open pre-push hook `.claude/hooks/prepush-clean-worktree-check.sh`, wired into `.husky/pre-push` after the H11 rebase check (and before the slow web CI-parity build, so it fails fast). It **blocks a push when a file in the branch's diff vs `origin/main` also has uncommitted working-tree or index changes** — i.e. the pushed commit wouldn't match what you built/reviewed.
- **Precise scope** (low false-positive): only files in the pushed diff; unrelated uncommitted scratch and untracked files never block.
- **Fail-open** on anything undecidable (not a repo, offline, no `origin/main`); deliberate escape `ETV_ALLOW_DIRTY_PUSH=1`.
- Same hook family / rationale as H11 (rebase-not-merge) and H12 (issue-qualification): "make the process rule a hook, not prose to remember" (#303).
## Verification
- Local: shellcheck clean; 4-case matrix — dirty PR-file → **block**; clean → allow; dirty **non**-PR file → allow; escape hatch → allow. Dogfooded: this very push ran through the new hook (clean tree → allowed).
- Cold review (review-only agent): **MERGEABLE**, no blocker, no false-allow. One MEDIUM noted — pushing already-committed work while deliberately keeping uncommitted WIP in the *same* file is blocked; that's by-design (the escape hatch covers it), and for our commit-then-push worktree flow it's rare. A warning instead of a block would just be ignored, so the block stays.
## Docs
`docs/decisions.md` entry (folds into `docs/decisions/release-ci-governance.md` alongside H11/H12 at the next release consolidation).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Cold-context review-only agent, read the committed hook + .husky/pre-push wiring + the H11 sibling for house style.
No false-allow of the target bug: dirty = git diff ∪ git diff --cached is a superset of "worktree ≠ committed(HEAD)", so the #416 trap (fix in worktree, stale index committed) is caught. origin/main...HEAD correctly scopes the pushed diff; comm -12 inputs identically sort -u'd (space-paths verified on macOS).
Fail-open confirmed: set -uo pipefail without -e; every undecidable branch is an explicit || exit 0 / -z guard; no pipeline $? tested except via ||, so pipefail can't spuriously block. Runs under its own bash shebang (mode 100755), so <(...)/comm are fine regardless of husky's launcher.
MEDIUM (by-design): pushing committed work while keeping uncommitted WIP in the same pushed-diff file is blocked; escape ETV_ALLOW_DIRTY_PUSH=1. Kept as a block (a warning would be ignored; rare in our commit-then-push flow). NITs: redundant git fetch after H11 (harmless, keeps the hook self-contained); docs updated in-PR (good).
Review-verdict: MERGEABLE @ f59cd187
Cold-context review-only agent, read the committed hook + `.husky/pre-push` wiring + the H11 sibling for house style.
- **No false-allow of the target bug**: `dirty` = `git diff` ∪ `git diff --cached` is a superset of "worktree ≠ committed(HEAD)", so the #416 trap (fix in worktree, stale index committed) is caught. `origin/main...HEAD` correctly scopes the pushed diff; `comm -12` inputs identically `sort -u`'d (space-paths verified on macOS).
- **Fail-open confirmed**: `set -uo pipefail` without `-e`; every undecidable branch is an explicit `|| exit 0` / `-z` guard; no pipeline `$?` tested except via `||`, so `pipefail` can't spuriously block. Runs under its own `bash` shebang (mode 100755), so `<(...)`/`comm` are fine regardless of husky's launcher.
- All adversarial cases pass (non-pushed dirty file → allow; untracked → allow; staged-uncommitted → block; clean/amend → allow; detached HEAD; first push; offline → allow).
- **MEDIUM (by-design)**: pushing committed work while keeping uncommitted WIP in the *same* pushed-diff file is blocked; escape `ETV_ALLOW_DIRTY_PUSH=1`. Kept as a block (a warning would be ignored; rare in our commit-then-push flow). NITs: redundant `git fetch` after H11 (harmless, keeps the hook self-contained); docs updated in-PR (good).
Review-verdict: MERGEABLE @ f59cd187
Rebased onto current main (1b355660, past #422/#423) to resolve a docs/decisions.md EOF-append conflict with #423 — kept both entries in order (#416 docs-only entry from main, then the H13 entry). Verified: git diff f59cd187 0f29da8f -- .claude/hooks/prepush-clean-worktree-check.sh .husky/pre-push is empty (the reviewed hook + wiring are byte-identical); the only delta is the decisions.md conflict resolution (insertions only, no deletions; append-only guard passes).
Review-verdict: MERGEABLE @ 0f29da8f
Rebased onto current `main` (`1b355660`, past #422/#423) to resolve a `docs/decisions.md` EOF-append conflict with #423 — kept **both** entries in order (#416 docs-only entry from main, then the H13 entry). Verified: `git diff f59cd187 0f29da8f -- .claude/hooks/prepush-clean-worktree-check.sh .husky/pre-push` is **empty** (the reviewed hook + wiring are byte-identical); the only delta is the decisions.md conflict resolution (insertions only, no deletions; append-only guard passes).
Review-verdict: MERGEABLE @ 0f29da8f
Rebased onto current main (5e0c53c3, past #429/#426) to re-resolve the docs/decisions.md EOF-append conflict — kept all entries in order (incl. #429's shallow-fix entry), H13 last. git diff 0f29da8f 1df08e86 -- .claude/hooks/prepush-clean-worktree-check.sh .husky/pre-push is empty (reviewed hook + wiring byte-identical); only delta is the decisions.md re-resolution (insertions only, append-only guard passes).
Rebased onto current `main` (`5e0c53c3`, past #429/#426) to re-resolve the `docs/decisions.md` EOF-append conflict — kept all entries in order (incl. #429's shallow-fix entry), H13 last. `git diff 0f29da8f 1df08e86 -- .claude/hooks/prepush-clean-worktree-check.sh .husky/pre-push` is **empty** (reviewed hook + wiring byte-identical); only delta is the decisions.md re-resolution (insertions only, append-only guard passes).
Review-verdict: MERGEABLE @ 1df08e86
timothy
merged commit ba707f2e23 into main2026-07-17 23:25:31 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Refs #416 (structural follow-up from that session; not
fixes).Why
The #416 session shipped a PR whose committed tree lacked a fix that lived only in the working tree: a
git reset --soft+git addcommitted a stale index, so the push, CI, and a cold reviewer each saw a different tree — the reviewer "confirmed" a--no-renamesfix that hadn't been committed. Local build/test/review operate on the working tree; what ships is the committed tree, and nothing enforced that they match. This is the mechanized half of that lesson (the review-process half — point reviewers atgit show <sha>:<file>— stays guidance).What
New fail-open pre-push hook
.claude/hooks/prepush-clean-worktree-check.sh, wired into.husky/pre-pushafter the H11 rebase check (and before the slow web CI-parity build, so it fails fast). It blocks a push when a file in the branch's diff vsorigin/mainalso has uncommitted working-tree or index changes — i.e. the pushed commit wouldn't match what you built/reviewed.origin/main); deliberate escapeETV_ALLOW_DIRTY_PUSH=1.Verification
Docs
docs/decisions.mdentry (folds intodocs/decisions/release-ci-governance.mdalongside H11/H12 at the next release consolidation).🤖 Generated with Claude Code
Cold-context review-only agent, read the committed hook +
.husky/pre-pushwiring + the H11 sibling for house style.dirty=git diff∪git diff --cachedis a superset of "worktree ≠ committed(HEAD)", so the #416 trap (fix in worktree, stale index committed) is caught.origin/main...HEADcorrectly scopes the pushed diff;comm -12inputs identicallysort -u'd (space-paths verified on macOS).set -uo pipefailwithout-e; every undecidable branch is an explicit|| exit 0/-zguard; no pipeline$?tested except via||, sopipefailcan't spuriously block. Runs under its ownbashshebang (mode 100755), so<(...)/commare fine regardless of husky's launcher.ETV_ALLOW_DIRTY_PUSH=1. Kept as a block (a warning would be ignored; rare in our commit-then-push flow). NITs: redundantgit fetchafter H11 (harmless, keeps the hook self-contained); docs updated in-PR (good).Review-verdict: MERGEABLE @ f59cd187
f59cd18781to0f29da8f00Rebased onto current
main(1b355660, past #422/#423) to resolve adocs/decisions.mdEOF-append conflict with #423 — kept both entries in order (#416 docs-only entry from main, then the H13 entry). Verified:git diff f59cd187 0f29da8f -- .claude/hooks/prepush-clean-worktree-check.sh .husky/pre-pushis empty (the reviewed hook + wiring are byte-identical); the only delta is the decisions.md conflict resolution (insertions only, no deletions; append-only guard passes).Review-verdict: MERGEABLE @ 0f29da8f
0f29da8f00to1df08e86bbRebased onto current
main(5e0c53c3, past #429/#426) to re-resolve thedocs/decisions.mdEOF-append conflict — kept all entries in order (incl. #429's shallow-fix entry), H13 last.git diff 0f29da8f 1df08e86 -- .claude/hooks/prepush-clean-worktree-check.sh .husky/pre-pushis empty (reviewed hook + wiring byte-identical); only delta is the decisions.md re-resolution (insertions only, append-only guard passes).Review-verdict: MERGEABLE @
1df08e86