What is there
Maintainer clones carry an untracked .git/hooks/pre-push:
#!/bin/bash
# Pre-push hook: run the test suite before pushing
echo "Running tests before push..."
cd "$(git rev-parse --show-toplevel)"
python3 -m pytest tests/ -q
exit $?
Nothing in the tree installs it and nothing documents it — grep pre-push over the repo finds only
CHANGELOG.md, hooks.d/after_save/50-git-backup.sh and tests/test_git_backup_push_rejected_253.py,
and all three are about parsing around a pre-push hook's output, not about installing one. So this
is per-machine state that every push pays for and no new contributor inherits.
Why it should go
CI is strictly stronger. .github/workflows/tests.yml runs on push: [main] and on every
pull_request, across ubuntu-latest/macos-latest/windows-latest × Python 3.9–3.12 — 12 legs.
The hook runs one OS and one interpreter, which is the platform this repo's own contributor guidance
calls "the weakest evidence available" about the platforms it was not run on. A green hook and a red
matrix is the normal outcome, not the surprising one, and the hook is what makes the push slow enough
that the matrix result is the one nobody waits for.
It has stopped being merely slow and started failing pushes. Measured on 2026-08-18 pushing one
one-line config change to chore/oss-scaffold:
- push 1 — killed at the 1500 s budget, still inside the hook, remote unmoved;
- push 2 — budget raised to 1800 s. The suite passed (
1576 passed, 43 skipped in 1674.11s) and then
the push died on Connection to github.com closed by remote host. The transport is opened before the
hook runs, so a 28-minute hook holds an idle SSH connection until the remote hangs up. git reported
no ref status at all: the push was stopped before it reached the remote;
- push 3 —
--no-verify, pushed in 5.8 s.
About 53 minutes to push one line, and the failing mode is the bad one: the suite passes, the push does
not, and the receipt says the remote closed the connection. The workaround that works is skipping the
hook, which is the same thing as not having it, arrived at once per push under time pressure.
It also runs the wrong suite. pytest tests/ -q with no -p no:cacheprovider, no coverage gate and
no marker selection is not what CI runs, so a pass here is not evidence about the gate that actually
blocks a merge.
Proposed change
- Remove
.git/hooks/pre-push from maintainer clones. There is nothing to delete in the tree.
- Say so in
CLAUDE.md under Before you open a pull request: the suite is run locally on demand and
the gate is CI, so nobody re-adds this hook from memory. That sentence is the only durable part of
this issue — everything else is one rm on one machine.
If a local gate is wanted, the honest shape is a fast one that cannot hold a transport open: a hook that
runs a marked subset in seconds, or a pytest invocation the contributor runs before git push rather
than during it.
Not this issue
Whether the 28-minute suite is itself too slow. It is the same 1576 tests CI runs in parallel across 12
legs; serialising them on one machine is the cost being questioned here, not the test count.
What is there
Maintainer clones carry an untracked
.git/hooks/pre-push:Nothing in the tree installs it and nothing documents it —
grep pre-pushover the repo finds onlyCHANGELOG.md,hooks.d/after_save/50-git-backup.shandtests/test_git_backup_push_rejected_253.py,and all three are about parsing around a pre-push hook's output, not about installing one. So this
is per-machine state that every push pays for and no new contributor inherits.
Why it should go
CI is strictly stronger.
.github/workflows/tests.ymlruns onpush: [main]and on everypull_request, acrossubuntu-latest/macos-latest/windows-latest× Python 3.9–3.12 — 12 legs.The hook runs one OS and one interpreter, which is the platform this repo's own contributor guidance
calls "the weakest evidence available" about the platforms it was not run on. A green hook and a red
matrix is the normal outcome, not the surprising one, and the hook is what makes the push slow enough
that the matrix result is the one nobody waits for.
It has stopped being merely slow and started failing pushes. Measured on 2026-08-18 pushing one
one-line config change to
chore/oss-scaffold:1576 passed, 43 skipped in 1674.11s) and thenthe push died on
Connection to github.com closed by remote host. The transport is opened before thehook runs, so a 28-minute hook holds an idle SSH connection until the remote hangs up.
gitreportedno ref status at all: the push was stopped before it reached the remote;
--no-verify, pushed in 5.8 s.About 53 minutes to push one line, and the failing mode is the bad one: the suite passes, the push does
not, and the receipt says the remote closed the connection. The workaround that works is skipping the
hook, which is the same thing as not having it, arrived at once per push under time pressure.
It also runs the wrong suite.
pytest tests/ -qwith no-p no:cacheprovider, no coverage gate andno marker selection is not what CI runs, so a pass here is not evidence about the gate that actually
blocks a merge.
Proposed change
.git/hooks/pre-pushfrom maintainer clones. There is nothing to delete in the tree.CLAUDE.mdunder Before you open a pull request: the suite is run locally on demand andthe gate is CI, so nobody re-adds this hook from memory. That sentence is the only durable part of
this issue — everything else is one
rmon one machine.If a local gate is wanted, the honest shape is a fast one that cannot hold a transport open: a hook that
runs a marked subset in seconds, or a
pytestinvocation the contributor runs beforegit pushratherthan during it.
Not this issue
Whether the 28-minute suite is itself too slow. It is the same 1576 tests CI runs in parallel across 12
legs; serialising them on one machine is the cost being questioned here, not the test count.