Development record
git push can report success while pushing nothing, when the pre-push preflight outlasts the SSH idle timeout. The hook runs the full preflight, which takes minutes, and the connection is opened before the hook runs, so the server closes it mid-hook: the push dies with a connection-closed message, the remote ref is unchanged, and the preflight's own passing output scrolls past the failure so the whole thing reads as a clean run. Hit twice in one session. The second time the push was also piped to another command, which returns that command's exit status, so a failed push reported zero. Remedies that worked: set SSH keepalive options for the push, never pipe the push, and confirm with a remote ref listing that the tip actually moved. Worth considering whether the hook should run before the connection is opened, or whether a push wrapper should verify the remote tip afterwards; the silent half is the defect, not the slowness.
git push can report success while pushing nothing, when the pre-push preflight outlasts the SSH idle timeout. The hook runs the full preflight, which takes minutes, and the connection is opened before the hook runs, so the server closes it mid-hook: the push dies with a connection-closed message, the remote ref is unchanged, and the preflight's own passing output scrolls past the failure so the whole thing reads as a clean run. Hit twice in one session. The second time the push was also piped to another command, which returns that command's exit status, so a failed push reported zero. Remedies that worked: set SSH keepalive options for the push, never pipe the push, and confirm with a remote ref listing that the tip actually moved. Worth considering whether the hook should run before the connection is opened, or whether a push wrapper should verify the remote tip afterwards; the silent half is the defect, not the slowness.
Grounds
- pursued: no push waits on a gate while its connection is open, so an idle-timeout close can no longer eat a push; a pre-push hook that still runs a multi-minute step, or a plain git push of an unpreflighted new commit that reaches the remote, would show it wrong
Frontmatter
| id | iss-2608290810036869 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-2608290810036869-git-push-can-report-success-while.md
open on GitHub ↗ · commit history ↗
mentions2
- dcn-26092300000000012026-09-23 — The product thinker ruled on the owed items of the autonomous run of 2026-09-23 in one interview, one question at a time (07:51Z to 09:32Z, while the run was paused): two release blockers, two intents, the 35 major captures routed to the product thinker, and two older owed rulings. The release waits for two things: version-stamped plugin payloads (iss-2609230733497536, built by a run lane, with no criterion amended and no deferral) and a release press release composed at the cut from the shipped intents' press releases, which is a new capability and gets an intent and its planning interview before any lane builds it. The run may approve the release environment itself once every gate is green; that agenda line is written in the release commit, not here. itd-4's AC3 is built as written, the `--issue-drift` check and the rename of the back-links to `related_issues` / `related_intents` together, and spc-6 closes only when both land (the 2026-07-17 ruling stands). itd-43's third criterion is moot and dropped, so spc-8 closes. The guard contract is extended to read a host-supplied per-call working directory (iss-2609212142557657, option A), after a probe of what the host does when none is supplied. Four majors are ruled to a build (iss-2608231120121681 write-path smoke, iss-2608290810036869 preflight before the push connects, iss-2609090828371674 per-agent staging locks, iss-2609120505141653 the provenance comment) and do not hold the tag. The rulings that set a rule each have their own entry below; every other routed major is deferred past v0.9.0 with its ruling as the `deferral_reason`, and the 168 routed minor and nitpick captures take the run's default deferral. The product thinker also asked that the interview be kept as autonomous-run experiment data, towards making a run transparent to the person it serves; the observations feed the run's end-of-run research note. Rejected, as the frame: the run taking any of these rulings itself.
- dcn-26092500000000032026-09-25 — A push is gated before it opens its connection, by receipt (iss-2608290810036869, iss-2608210738378295; product thinker's ruling M16 of 2026-09-23, "check before connect"). The committed `.githooks/pre-push` no longer runs `make preflight`: git opens the connection before the hook, and a preflight inside it outlasted the transport's idle timeout, so a push reported success and moved nothing. `make preflight` ends by minting a receipt for HEAD under the checkout's local tier (`scripts/preflight-receipt.sh`), and only when the working tree matched HEAD (nothing staged, unstaged or untracked) both when the run began, read while the Makefile is parsed, and when it ended, with HEAD unmoved, so the gates read the tree CI checks out. The hook refuses a push whose commit is new to the remote and carries no receipt from any worktree of the repository; a commit the remote already holds (a tag on a merged commit) passes. Alternatives not taken: a push wrapper that runs the preflight and then `git push --no-verify` (it normalises `--no-verify`, and a plain `git push` would go ungated); keepalive settings on the transport (the hook would still hold the connection for ten minutes); a preflight on a clean export of HEAD (a second full tree and build per push, where refusing a divergent tree costs nothing). `git push --no-verify` skips the hook exactly as before, and CI stays the authority (lane hooks, autonomous run A).