Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions .claude/skills/ci-gates/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17232,13 +17232,29 @@ an **all-zero local sha**.
--amend yes yes
--allow-empty yes yes
merge --no-ff NO yes
cherry-pick NO NO
cherry-pick NO NO (but prepare-commit-msg fires -- see below)

So every gate in the barrier — the conflict-marker refusal above all — was silent on **the
one commit type conflict markers come from**. `git merge` runs `pre-merge-commit`, whose
non-zero exit stops the merge; the index at that moment holds the merge RESULT, which is
exactly the operand the barrier reads once it is corrected to `--staged`. `cherry-pick` runs
neither hook and git offers none that could stop it: that gap is stated, not papered over.
exactly the operand the barrier reads once it is corrected to `--staged`.

`cherry-pick` runs `prepare-commit-msg` and `post-commit`.

**A first version of this section said cherry-pick could not be stopped by any hook, and
that was wrong** -- because the probe carried markers for only SIX hook names. "Nothing
fired" can mean "I did not look". Re-measured over the full set of thirteen:

cherry-pick prepare-commit-msg, post-commit
git am applypatch-msg, pre-applypatch, post-applypatch
rebase NOTHING

A non-zero exit from `prepare-commit-msg` aborts a cherry-pick (exit 128, no commit) and
from `applypatch-msg` aborts a `git am` (exit 1, no commit). Both are covered now;
`rebase` genuinely is not, and that one is the gap.

The correction is the lesson: **the population of a probe is as narrow as its instrument
list**, and an empty result from a narrow instrument is indistinguishable from an absence.

**The method is enumeration, not cleverness.** Write marker hooks that only `touch` a file,
run each event through them, and read which files exist. It takes minutes and answers a
Expand Down
15 changes: 15 additions & 0 deletions .githooks/applypatch-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# L1 for `git am`, the analogue of commit-msg. git passes the message file as $1.
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
TRI_BIN=""
for cand in "$ROOT/target/debug/tri" "$ROOT/target/release/tri"; do
[ -x "$cand" ] || continue
if [ -z "$TRI_BIN" ] || [ "$cand" -nt "$TRI_BIN" ]; then TRI_BIN="$cand"; fi
done
if [ -z "$TRI_BIN" ]; then
echo "note: no tri binary; L1 was not checked and nothing about this message is claimed."
exit 0
fi
exec "$TRI_BIN" hooks commit-msg "$1"
17 changes: 17 additions & 0 deletions .githooks/pre-applypatch
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# The barrier for `git am`. It runs once per patch with the index already holding the
# applied patch -- the same operand `tri hooks pre-commit` reads since it was corrected
# to `--staged`. A non-zero exit stops the apply; verified, exit 1 and no commit created.
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
TRI_BIN=""
for cand in "$ROOT/target/debug/tri" "$ROOT/target/release/tri"; do
[ -x "$cand" ] || continue
if [ -z "$TRI_BIN" ] || [ "$cand" -nt "$TRI_BIN" ]; then TRI_BIN="$cand"; fi
done
if [ -z "$TRI_BIN" ]; then
echo "note: no tri binary; this patch was not checked and nothing about it is claimed."
exit 0
fi
exec "$TRI_BIN" hooks pre-commit
38 changes: 38 additions & 0 deletions .githooks/prepare-commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# The barrier for a CHERRY-PICK, and only for a cherry-pick.
#
# MEASURED with marker hooks on git 2.50.1 over the full hook set (13 names, not the six
# a narrower probe used -- "nothing fired" can mean "I did not look"):
#
# event hooks that fire
# normal commit pre-commit, prepare-commit-msg, commit-msg, post-commit
# merge --no-ff pre-merge-commit, prepare-commit-msg, commit-msg
# cherry-pick prepare-commit-msg, post-commit
# git am applypatch-msg, pre-applypatch, post-applypatch
# rebase NOTHING
#
# A previous note in docs/now claimed cherry-pick could not be stopped by any hook. That
# was wrong, and the correction is this file: a non-zero exit from prepare-commit-msg
# aborts the cherry-pick -- verified, exit 128 and no commit created.
#
# It must do nothing on an ordinary commit, where pre-commit has already run and running
# again would double the barrier's cost for no answer. `.git/CHERRY_PICK_HEAD` is the
# definitive marker of a cherry-pick in progress.
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"

GITDIR="$(git rev-parse --git-dir)"
[ -e "$GITDIR/CHERRY_PICK_HEAD" ] || exit 0

TRI_BIN=""
for cand in "$ROOT/target/debug/tri" "$ROOT/target/release/tri"; do
[ -x "$cand" ] || continue
if [ -z "$TRI_BIN" ] || [ "$cand" -nt "$TRI_BIN" ]; then TRI_BIN="$cand"; fi
done
if [ -z "$TRI_BIN" ]; then
echo "note: no tri binary; this cherry-pick was not checked and nothing about it is claimed."
exit 0
fi
echo "cherry-pick: running the barrier, which pre-commit does not see."
exec "$TRI_BIN" hooks pre-commit
2 changes: 1 addition & 1 deletion .trinity/notebook_commit_count
Original file line number Diff line number Diff line change
@@ -1 +1 @@
12
13
24 changes: 24 additions & 0 deletions docs/now/2026-09-05-cherry-pick-can-be-stopped-after-all.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# NOW -- cherry-pick can be stopped after all, and my note said it could not (2026-09-05)

## cherry-pick can be stopped after all (Refs #3329)

- Section 594 and its note said `cherry-pick` runs neither commit hook and git offers
none that could stop it. That was wrong, and the reason is worth more than the fix.
- The probe carried markers for SIX hook names. `cherry-pick` fires
`prepare-commit-msg`, and `git am` fires `applypatch-msg` and `pre-applypatch` --
none of which were in the list. **"Nothing fired" can mean "I did not look".**
- Re-measured over the full thirteen. A non-zero exit from `prepare-commit-msg` aborts a
cherry-pick (128, no commit) and from `applypatch-msg` aborts a `git am` (1, no
commit). Verified directly, both.
- Three hooks added. `prepare-commit-msg` gates only when `.git/CHERRY_PICK_HEAD`
exists, so an ordinary commit still pays exactly one barrier run -- verified, and the
first attempt at that control was void because the commit had been refused by the census
for an unrelated reason.
- Controls: a cherry-pick of a commit carrying a conflict marker exits 128 with
`carries a conflict marker` and creates nothing; the same through `git am` exits 1
with the same sentence, once the index is clean -- the first run of that control was also
void, refused by git for a dirty index rather than by the hook.
- `rebase` fires nothing at all and remains genuinely uncovered. That gap is real.
- Blessing two census moves that are NOT mine: `quiet` 128 to 127 and `shell` 235 to
234, from a neighbour repairing two workflows. Confirmed on a clean origin/master
checkout with the same binary before blessing, rather than assumed.
2 changes: 1 addition & 1 deletion tools/census/quiet.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ GATE STEPS WHOSE PASS SURVIVES THE SUBJECT GOING MISSING

workflow files read 50
steps in a quiet shape 31
named a path but not quiet 128 (--excluded prints them)
named a path but not quiet 127 (--excluded prints them)

by shape:
failure branch passes 15 `… 2>/dev/null … || echo PASSED`
Expand Down
4 changes: 2 additions & 2 deletions tools/census/shell.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ WHICH INTERPRETER EACH GATE STEP IS HANDED TO, AND WHO SAYS SO

workflow files read 50
jobs 71
run: steps 235
run: steps 234

who names the shell:
the runner does 214 no container, so bash -eo pipefail
the runner does 213 no container, so bash -eo pipefail
a `shell:` key does 0
NOBODY 21 a container and no `shell:` key

Expand Down
Loading