Skip to content

ci: abandon the pre-push hook, detect the orphan instead (C-340, #426) - #439

Closed
Polichinel wants to merge 5 commits into
developmentfrom
fix/replace-hook-with-detector
Closed

ci: abandon the pre-push hook, detect the orphan instead (C-340, #426)#439
Polichinel wants to merge 5 commits into
developmentfrom
fix/replace-hook-with-detector

Conversation

@Polichinel

Copy link
Copy Markdown
Contributor

Epic #421 Story 5 (#426). Supersedes #438 (closed unmerged). The deliverable is a removal.

What this does

  • Deletes scripts/git-hooks/pre-push and tests/test_git_hooks.py
  • Keeps scripts/arm_automerge.sh — mechanism 1 works and was drilled live
  • Adds an orphan detector to release-topology.yml (daily cron, folded into the existing single reusable issue)
  • Narrows C-340 in the register; C-340 stays open on the residue

Why the hook was abandoned

Four versions, four different defeats:

ver rule defeated by
v1 git rev-parse HEAD + branch name checked the wrong branch — git supplies pushed refs on stdin; and permanently refused reused names (docs/roadmap-plan-v11 spans #50–54)
v2 merged head is an ancestor of the push a merge-commit merge puts that head into the base branch forever — PR #54's is an ancestor of development today
v3 remote_sha equals the merged head delete_branch_on_merge removes the branch first, so git reports 0000…. Proved with a two-clone experiment
v4 ancestor AND not in base never shipped

The test suite was vacuous: reconstructing v1 and running all seven behavioural tests passed every one.

It recurred while being fixed. #437 auto-merged carrying broken v1; the review fixes were pushed to that branch afterwards and orphaned — the same defect inside the PR addressing it. The hook was not installed in that clone, because core.hooksPath is per-clone config git does not version. That is itself the argument against an install-it-yourself guard.

Four failures from four different environment properties is the signature of inferring state you cannot see. A multi-expert panel converged independently: a client check races an asynchronous deletion (a property, not a bug); the module is shallow and its complexity is entirely special cases; the failure occurred twice in ~440 PRs, both recovered by cherry-pick inside the hour. Their verdict on the warning variant was sharpest — a notice printed on every push goes invisible in a week, which is the exact fails-green shape this epic exists to remove.

Why detection works where prevention did not

The framing was the error. The orphaned state is unambiguous once things settle: a remote branch with commits beyond its merged PR's head and no open PR. Two questions, no ancestry subtleties, no merge-method dependence, nothing to install, and it cannot block anyone's work.

Measured against the merged PR's head, not development — merges here are squashes, so a branch's own commits are never ancestors of development and the naive comparison would flag every merged branch.

Drill

End-to-end against live state, with the body extracted verbatim from the workflow rather than retyped:

  • found the genuine orphan — ops/pre-push-hook-and-automerge — 1 commit(s) pushed AFTER PR #437 merged, no open PR
  • correctly ignored fix/pre-push-hook-review-findings (PR closed-unmerged)
  • reported orphans=false after both stale branches were deleted from the remote

Residue — #428 must say this

Detection is not prevention, and nothing forces arm_automerge.sh: gh pr merge --auto is one keystroke away. C-340 stays open.

Gates

validate_docs.sh PASSED · ruff check . All checks passed · PYTEST_EXIT=0 · FAILED: 0

🤖 Generated with Claude Code

The deliverable is a removal. Four versions of a client-side guard, four
different defeats:

  v1  HEAD + branch name   checked the wrong branch (git supplies pushed
                           refs on stdin); permanently refused reused
                           names — docs/roadmap-plan-v11 spans #50-54
  v2  merged head is an    a merge-commit merge puts that head in the base
      ancestor             branch forever, so it is an ancestor of every
                           later branch. PR #54's is in development today
  v3  remote_sha equals    delete_branch_on_merge removes the branch first,
      the merged head      so git reports 0000... — proved with a two-clone
                           experiment
  v4  ancestor AND not     never shipped
      in base

The test suite was vacuous: reconstructing v1 and running all seven
behavioural tests passed every one.

It recurred while being fixed. #437 auto-merged carrying broken v1; the
review fixes were pushed to that branch afterwards and orphaned — the
same defect inside the PR addressing it. The hook was not installed in
that clone, because core.hooksPath is per-clone config git does not
version. That is the argument against an install-it-yourself guard.

Abandoned rather than fixed a fifth time. Four failures from four
DIFFERENT environment properties is the signature of inferring state you
cannot see. A multi-expert panel converged independently: a client check
races an asynchronous deletion (a property, not a bug); the module is
shallow and its complexity is entirely special cases; twice in ~440 PRs,
both recovered by cherry-pick inside the hour. Their verdict on the
warning variant was sharpest — a notice printed on every push goes
invisible in a week, which is the exact fails-green shape this epic
exists to remove.

The framing was the error. The orphaned STATE is unambiguous once things
settle: a remote branch with commits beyond its merged PR's head and no
open PR. release-topology.yml already had fetch-depth: 0, issues: write,
a daily cron and one-reusable-issue machinery.

Drilled end-to-end with the body extracted from the workflow, not
retyped: found the genuine orphan ("1 commit(s) pushed AFTER PR #437
merged, no open PR"), ignored a branch whose PR was closed-unmerged, and
reported clean once it was deleted. Measured against the merged head
rather than development — merges here are squashes, so a branch's own
commits are never ancestors of development and the naive comparison would
flag every merged branch.

scripts/arm_automerge.sh is kept: mechanism 1 works and was drilled live.
C-340 stays OPEN on the residue — detection is not prevention, and
nothing forces the script's use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Polichinel

Copy link
Copy Markdown
Contributor Author

Code review

Found 5 issues. The first three are all the same shape the PR exists to remove: the detector reports orphans=false while establishing nothing.

  1. The workflow's permissions: block does not grant pull-requests, and gh pr list is the detector's entire mechanism. An explicit permissions: block sets every unlisted scope to none, so both gh pr list calls get a 403 — which 2>/dev/null) || continue makes indistinguishable from "no PR found". Every branch is skipped, found stays empty, and the step prints "No orphaned branches" forever, green. This is the first gh pr call in any workflow here (grep -rl 'gh pr ' .github/workflows/ matches only this file), so there is no precedent proving it works. Needs pull-requests: read — and a live run to confirm, since the local drill ran under personal gh auth, not GITHUB_TOKEN.

permissions:
contents: read
issues: write

  1. Reused branch names produce false positives — the same property that defeated hook v1. The merged PR is matched by name alone, with no ancestry check against the current tip. Confirmed reused in this repo: gh pr list --state all --limit 300 --json headRefName --jq '.[].headRefName' | sort | uniq -d returns chore/version-bump-1.2.13, docs/roadmap-plan-v11, feat/acled-phase2. Cut a new branch on one of those names, push it, don't open the PR yet: not in development, no open PR, an old merged PR exists, tip != old head — flagged. Worse, the old head is not in the new branch's ancestry, so git rev-list --count at L193 fails and stranded becomes ?; verified locally, the guard [ "$stranded" = "0" ] does not catch it and the issue reads "? commit(s) pushed AFTER PR docs: risk register update — C-168, C-169, C-155 #54 merged".

# merged, the branch outlived it — that is the #416 shape.
pr=$(gh pr list --head "$branch" --state merged \
--json number,headRefOid --limit 1 2>/dev/null) || continue
merged=$(printf '%s' "$pr" | sed -n 's/.*"number":\([0-9]*\).*/\1/p')
head=$(printf '%s' "$pr" | sed -n 's/.*"headRefOid":"\([^"]*\)".*/\1/p')
[ -n "$merged" ] && [ -n "$head" ] || continue

  1. A transient git ls-remote failure yields a silent all-clear. GitHub Actions runs bash -e {0}, and set -uo pipefail does not remove -e — but a failing command substitution in a for word list is exempt. Verified: bash -e -c 'for x in $(false | sed s/a/b/); do echo BODY; done; echo END' prints only END. One network hiccup and the loop never runs, found is empty, and the step reports clean.

found=""
for branch in $(git ls-remote --heads origin | sed 's|.*refs/heads/||'); do
case "$branch" in main|development) continue ;; esac

  1. New ordering dependency, ungoverned. L167 needs origin/development, which comes from the "Fetch both long-lived branches" step 100 lines earlier. Reorder or drop that step and git merge-base exits 128 into 2>/dev/null, the && short-circuits so the skip does not fire, and every branch falls through to the PR checks. tests/test_ci_gates.py exists for exactly this — its docstring says "getting the order wrong produces a green run rather than a red one" — but no assertion was added for this dependency, or for the new step at all.

# Already in development: nothing is stranded.
git merge-base --is-ancestor "$tip" origin/development 2>/dev/null && continue

  1. A guard was deleted along with the code it did not guard. tests/test_git_hooks.py is removed wholesale, but it also held test_arm_helper_exists_and_is_executable, covering scripts/arm_automerge.sh — which this PR keeps and publishing_to_pypi.md tells operators to run directly. grep -rl arm_automerge tests/ now returns nothing. Its own docstring is the argument for restoring it: "A mode lost to a rebase, a patch, or a filesystem copy is invisible."

```bash
scripts/arm_automerge.sh <pr-number> <merge|squash|rebase>
```

Two lower-confidence observations, not blocking: the orphans step has no continue-on-error/outcome capture (unlike the sibling gates step), so if it ever hard-fails, steps.orphans.outputs.orphans is empty and the "Close the tracking issue" condition can never be met; and the recovery snippet is stated in both the generated issue body and the guide with no declared precedence, which is the shape #431 fixed by naming a winner.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Polichinel and others added 2 commits August 12, 2026 13:03
…349)

Addresses every finding from /code-review and /review-diff on #439. The
detector shipped in the previous commit would have reported "No orphaned
branches" forever, green, on a daily cron.

WHY THE DRILL MISSED ALL OF IT — C-347, Tier 2
It ran under the operator's personal `gh` credentials, which carry full
scope. Production runs under GITHUB_TOKEN, and an explicit permissions:
block sets every UNLISTED scope to `none` — so `pull-requests` was
revoked and both `gh pr list` calls would 403 into `2>/dev/null ||
continue`, indistinguishable from "no PR found". The drill obtained and
reported its result correctly; it simply ran in the wrong world. That is
neither C-345 (an instrument misreading a result it did obtain) nor
C-336 (a guard narrower than its property) but a third thing, and it has
now fired four times in one week, each from a different environment
property: `gh` never hidden because it lives in ~/.local/bin; a `git
checkout` silently refused so a "want FAIL" case ran on-tag; pytest run
from the main repo while the test shelled out to `git describe`; this.

FIXES
  permissions   `pull-requests: read`, with the reasoning inline
  reuse         require the merged head to be a genuine ANCESTOR of the
                tip. Names are reused here — chore/version-bump-1.2.13,
                docs/roadmap-plan-v11, feat/acled-phase2 have each headed
                more than one PR — and matching by name alone was the
                property that defeated hook v1, surviving into its
                replacement. Plus `--not origin/development` for the
                pre-#54 merge-commit era
  blind loop    `for x in $(cmd)` swallows a failure of cmd completely:
                GHA runs `bash -e {0}`, `set -uo pipefail` does not
                remove -e, and a `for` word list is exempt. Verified:
                `bash -e -c 'for x in $(false|sed s/a/b/); do echo BODY;
                done; echo END'` prints only END
  per-branch    every `|| continue` turned "could not check" into "this
                branch is fine". Now counted; a partial result is
                qualified; answering for NOTHING exits non-zero, because
                a clean report resting on zero observations is the defect
                itself, not a degraded mode of it

GUARDS, ALL DRILLED AGAINST THE BROKEN STATE WITH A CONTROL
test_ci_gates.py gains four assertions (the permission grant, the fetch
ordering, the blind-iteration idiom, the zero-answers rule). Revoke,
restore, reorder, remove: each reddened exactly the guard aimed at it.
The reused-name fix was drilled in a scratch repo — git ancestry does
not differ between machines — where the new logic reports the genuine
orphan as 1 stranded commit and SKIPS the namesake, while the old logic
reported the namesake as 3 commits stranded.

test_ops_scripts.py (new) restores the guard on arm_automerge.sh's
executable bit, deleted as collateral when test_git_hooks.py went with
the hook — C-348. A test module is an organisational unit that quietly
doubles as a coverage unit, and only the first is visible when you
delete it.

C-345 CORRECTED — ITS OWN PRESCRIBED MITIGATION IS DEFECTIVE
It recommends `grep -cE '^FAILED'` as a second independent reader.
pytest colourises, so the line is \033[31mFAILED\033[0m and the caret
never matches. The drill harness here reported "DID NOT CATCH" four
times while all four drills had caught. Corrected to --color=no at the
source. Second time an entry has prescribed a defective remedy (C-331's
unquoted printf 'url=%s' turned a failure ping into a success ping): a
mitigation written into an entry is untested code that inherits the
entry's credibility without earning it.

C-349 REGISTERED, DELIBERATELY NOT FIXED HERE — Tier 2
Generalising the per-branch finding found the same shape in production:
acled.py, ucdp.py and health.py all drop an unparseable ledger line with
a bare `except json.JSONDecodeError: continue`, uncounted, while
grid_compilation.py already does it right with n_skipped_spatial += 1.
The consolidators use the ledger to decide what to consolidate, so a
dropped line means a successful harvest is silently not consolidated.
An untested change to consolidation does not belong in a workflow PR.

validate_docs PASSED | ruff clean | PYTEST_EXIT=0 | FAILED=0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… fail

Second review round on #439 (/code-review max). Fifteen findings, verified
by extracting the shell step from the YAML and running it under `bash -e`
against synthetic repos with a stubbed gh. The round is worth more than
the story.

C-350 — THE DELIVERABLE WOULD NOT HAVE RUN
GitHub fires `schedule` from the DEFAULT BRANCH ONLY. Default here is
main; all work goes through development. So the detector merges to
development and the 06:00 cron keeps running main's copy, which has no
detector, until an irregular release promotion carries it over.
Measured: origin/main's workflow has ZERO occurrences of "orphan", and
every event:schedule run has headBranch:main.

Meanwhile the guide, the changelog and C-340's narrowing all said
"checks daily" as present fact, and the four new guards assert
properties of the BRANCH's file — green on every PR while the branch
that runs the cron has none of it. Every verification performed was
true and none of them was the question. A workflow_dispatch run proves
the code works; it says nothing about whether anything will call it.
All three claims corrected.

C-351 — A LIVE RED GATE NOBODY WAS READING
serving-freshness.yml has failed every scheduled run since at least
2026-08-08: no actions/checkout, so a git command aborts with `fatal:
not a git repository`. Freshness alerting for served data has been dead
five days. Registered immediately — "pre-existing" is not a disposition
this project accepts.

THREE OF MY OWN GUARDS COULD NOT FAIL FOR WHAT THEY CLAIMED
"answered=" is a SUBSTRING of "unanswered=", so the counting assertion
passed with every `answered` counter deleted. A bare `"::error::" in
run` over a 130-line body was satisfied by an unrelated guard a hundred
lines below, so deleting BOTH enumeration guards kept the suite green.
The class comment three lines above warns against asserting "the how,
not the what" and cites C-336. Rewritten with a lookbehind, anchored
patterns, and step lookup by stable `id:` instead of a substring of the
body under test — then drilled against the exact three defeats.

Rewriting them produced a fourth instance: the first replacement
matched the step's own COMMENTS, which quote the anti-patterns they
warn against, so it reddened the fixed file. Caught by the control run.
Comments are stripped before matching now — what test_heartbeat_secret
already learned, in this same epic.

C-345's ADDENDUM FROM THIS MORNING WAS WRONG — RETRACTED IN PLACE
It claimed pytest colourises so `grep -cE '^FAILED'` cannot match.
pytest does NOT colourise into a file; the ANSI came from FORCE_COLOR=3
in this operator's shell. Measured: with it, grep returns 0 on a
failing run; with `env -u FORCE_COLOR`, 1 and zero ANSI lines. C-345's
original prescription was fine in a plain shell and in CI. An anomaly
from an uncontrolled variable was diagnosed as a tool defect and
escalated into a general claim about the register — withdrawn. That is
C-347, committed inside the commit registering C-347.

C-349 UNDERCOUNTED ITS OWN LOCATIONS
Named 3 JSONDecodeError sites; the grep returns 7, and
digests_and_ledgers.py already logs the skip, falsifying the entry's
"nothing anywhere records that it happened". The grep is now in the
Location field so the count is re-derivable rather than asserted.

NOT FIXED, AND WHY THE STORY STOPS HERE
Nine verified behavioural findings remain: the prescribed recovery never
clears the alert (permanently red cron, permanently open issue); a
partial scan writes an unqualified orphans=false that auto-closes a
genuine orphan issue; the issue-reuse path never renders the orphan
section, so the daily comment names no branch; merge-base exit 128 is
booked as a definite answer INSIDE the accounting added to fix C-347;
two seds depend on gh's compact JSON; --limit 1 orders by createdAt not
mergedAt; a bare branch name in git fetch resolves refs/tags/ first.

Round one: five defects. Round two: fifteen, each from a different
property of git, gh, or GitHub. That is the signature that ended the
pre-push hook after four versions, reappearing in its replacement — and
the panel's argument applies unchanged: twice in ~440 PRs, both
recovered by cherry-pick inside the hour. The design question goes back
to the operator rather than being resolved by a third round.

validate_docs PASSED | ruff clean | PYTEST_EXIT=0 | FAILED=0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Polichinel
Polichinel marked this pull request as draft August 12, 2026 11:49
@Polichinel

Copy link
Copy Markdown
Contributor Author

Converted to draft — the ritual stopped at iteration 2 of 3

/code-review max returned 15 findings, verified empirically (the shell step was extracted from the YAML and run under bash -e against synthetic repos with a stubbed gh). The headline is that this PR's deliverable would never have run.

GitHub fires schedule from the default branch only. Default here is main; all work goes through development. So the detector merges to development and the 06:00 cron keeps executing main's copy — which contains no detector — until an irregular release promotion carries it over. Measured: origin/main's workflow has zero occurrences of orphan, and every event: schedule run has headBranch: main. Registered as C-350.

The guide, the changelog and C-340's narrowing all said "checks daily" as present fact. The four guards assert properties of the branch's file, so they go green on every PR while the branch that runs the cron has none of it. Every verification I performed was true and none of them was the question — a workflow_dispatch run proves the code works, not that anything will call it.

Fixed in 4ef1f37

  • All three "checks daily" claims corrected to state the constraint
  • Three of my four new guards could not fail for what they claimed — "answered=" is a substring of "unanswered=", and a bare "::error::" in run over a 130-line body was satisfied by an unrelated guard, so deleting both enumeration guards kept the suite green. Rewritten and drilled against the exact defeats. (Rewriting them produced a fourth instance: the replacement matched the step's own comments, which quote the anti-patterns they warn against.)
  • C-345's addendum from this morning is retracted — I claimed pytest colourises so grep -cE '^FAILED' cannot match. It does not; FORCE_COLOR=3 is set in my shell. C-345's original prescription was fine in a plain shell and in CI. I diagnosed an uncontrolled environment variable as a tool defect and escalated it into a general claim about the register. That is C-347, committed inside the commit registering C-347.
  • C-349 undercounted — 3 sites named, grep -rn JSONDecodeError src/ returns 7, and digests_and_ledgers.py already logs the skip
  • C-351 registered: serving-freshness.yml has failed every scheduled run since 2026-08-08 (no actions/checkout). Freshness alerting for served data has been dead five days. Separate fix.

Not fixed — nine verified behavioural findings

The prescribed recovery never clears the alert (permanently red cron, permanently open issue); a partial scan writes an unqualified orphans=false that auto-closes a genuine orphan issue; the reuse path never renders the orphan section, so the daily comment names no branch; merge-base exit 128 is booked as a definite answer inside the accounting added to fix C-347; two sed parsers depend on gh's compact JSON; --limit 1 orders by createdAt not mergedAt; a bare branch name in git fetch resolves refs/tags/ first.

Why it stops here rather than going to round three

Round one: five defects. Round two: fifteen, each from a different property of git, gh, or GitHub. That is the signature that ended the pre-push hook after four versions, reappearing in its replacement. The panel's argument applies unchanged — twice in ~440 PRs, both recovered by cherry-pick inside the hour. This needs a design decision, not a third pass.

Gates on the current head: validate_docs PASSED, ruff clean, PYTEST_EXIT=0, FAILED=0.

🤖 Generated with Claude Code

Second review round found 15 defects in the precise detector. This
replaces it with the blunt one.

WHAT IT DOES NOW
delete_branch_on_merge is on, so a branch normally disappears when its
PR merges. So: for each remote branch, is there an open PR? If not, say
so. No SHAs, no ancestry, no fetch, no merged-PR lookup, no rev-list.

WHAT THAT DELETES, AND WHY IT IS THE POINT
Nine of the fifteen findings had nowhere left to live: the name-matched
merged-PR lookup (names are reused here); `--limit 1` ordering by
createdAt rather than mergedAt; `git fetch <name>` resolving refs/tags/
before refs/heads/; `merge-base` exit 128 for a missing object booked as
a DEFINITE ANSWER — C-347 inside the accounting added to fix C-347; two
sed parsers depending on gh's compact JSON; and the latch, where the
recovery the issue body printed could never clear its own alert, so the
cron would have gone permanently red and the shared issue permanently
open.

Four hook versions and two detector versions, each defeated by a
different property of git, gh, or GitHub. Precision here requires
inferring state we cannot see, and every attempt to infer it acquires a
new special case. This version cannot be wrong about what it observed
because it observes almost nothing.

A branch pushed before its PR is opened gets reported. That is not
really a false positive — it is a branch with work on it and no route to
development.

FIXED RATHER THAN DROPPED
  three-valued  a PARTIAL scan writes `partial`, not `false`. `false` is
                what closes the tracking issue, so a degraded scan
                writing it would close a live orphan issue on the
                strength of a scan that skipped that very branch
  finding wins  `found` is tested before `partial`, or a real orphan
                discovered during a degraded scan is never reported
  reuse path    now renders the orphan section. It previously commented
                only "diverged=false, gates_failed=false" — on a red
                run, naming no branch, so the one actionable fact never
                reached the reader
  title         no longer asserts "deploy gates are failing" when only
                the orphan check fired; old titles kept in the close
                loop so no issue is stranded
  recovery      `gh pr create` or `git push --delete`, either of which
                actually clears the check

C-350 — IT WILL NOT RUN UNTIL PROMOTED
GitHub fires `schedule` from the default branch only; that is main, and
all work goes through development. Documented in the guide and the
register with the workflow_dispatch fallback, rather than left as the
claim that it "checks daily".

Guards updated and drilled against the exact defeats: collapse `partial`
into `false`, revoke the permission.

validate_docs PASSED | ruff clean | PYTEST_EXIT=0 | FAILED=0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Polichinel
Polichinel marked this pull request as ready for review August 12, 2026 14:20
@Polichinel

Copy link
Copy Markdown
Contributor Author

Simplified to one question — 3e1fa37

Per the design decision: the detector no longer tries to establish that work is definitely orphaned. delete_branch_on_merge is on, so a branch that still exists with no open pull request is already anomalous. That is the whole check.

Nine of the fifteen findings had nowhere left to live once the inference went: the name-matched merged-PR lookup, --limit 1 ordering by createdAt, git fetch <name> resolving refs/tags/ first, merge-base exit 128 booked as a definite answer, both sed JSON parsers, and the latch that would have kept the cron permanently red.

Fixed rather than dropped:

  • orphans is three-valued — a partial scan writes partial, not false. false is what closes the tracking issue, so a degraded scan writing it would have closed a live orphan issue on the strength of a scan that skipped that branch.
  • A finding is tested before partial, so a real orphan found during a degraded scan still opens an issue.
  • The reuse path now renders the orphan section. It previously commented only diverged=false, gates_failed=false — on a red run, naming no branch.
  • The title no longer claims deploy gates are failing when only the orphan check fired; old titles kept in the close loop.
  • Recovery is gh pr create or git push --delete — either actually clears the check, which the cherry-pick advice never did.

C-350 documented, not papered over. It still will not run on the cron until a release promotion carries it to main. The guide and register now say so and give the workflow_dispatch fallback.

Verified live (run 31606154293), by dispatching rather than drilling locally: Checked 1 branch(es); 0 could not be answered for.No branches without an open pull request. No issue opened, reporting steps correctly skipped.

Guards drilled against the exact defeats (collapse partial into false; revoke the permission). validate_docs PASSED, ruff clean, PYTEST_EXIT=0, FAILED=0.

🤖 Generated with Claude Code

Third review round. Corrections only — no behaviour change. At the
ritual's 3-iteration cap; remaining findings are reported, not fixed.

"BOTH RECOVERED BY CHERRY-PICK INSIDE THE HOUR" IS FALSE
The sole quantitative basis for abandoning the pre-push hook, cited to
Beck, repeated in the register, the changelog twice, and the guide.
Measured:

  #416 merged   2026-08-03T11:24:10Z
  #417 opened   2026-08-04T01:08:49Z   -> 13h 44m, not "inside the hour"
  #438          CLOSED, never merged   -> that recovery never landed

It cuts both ways and both are now stated: 14 hours to notice makes a
daily detector comparable rather than clearly worse, AND it removes the
"cheap to recover" premise the abandonment argument leaned on.

C-351's DATES WERE WRONG, AND THE ROOT CAUSE WITH THEM
Said "failed since at least 2026-08-08 — dead for five days", implying a
regression. `gh run list --workflow=serving-freshness.yml` returns 10
runs, 10 failures, ZERO successes, earliest 2026-08-03 — the day it was
added. It did not regress: it shipped broken and was never verified,
which makes it C-350's sibling rather than C-338's.

THE DOCUMENTED WORKAROUND FOR C-350 DID NOT WORK
`gh workflow run release-topology.yml` with no --ref dispatches the
DEFAULT branch's copy — main, which has no detector — and the run goes
green having executed none of it, which reads exactly like a clean
result. Both of my own dispatches used --ref, so I never exercised the
form I wrote down. Now `--ref development`, with the trap spelled out.

THE CHANGELOG'S "WHAT IS NOT FIXED" LISTED NINE DEFECTS THAT NO LONGER EXIST
3e1fa37 deleted the code seven of them lived in; two were fixed. Left
standing with its correction rather than rewritten, because #428 closes
the epic from this record and would otherwise schedule work against
defects nobody can reproduce (C-336).

validate_docs PASSED | ruff clean | PYTEST_EXIT=0 | FAILED=0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Polichinel

Copy link
Copy Markdown
Contributor Author

Closing unmerged, deliberately.

Cost/benefit, stated plainly. C-340 mechanism 2 is: a commit lands on a branch whose PR already merged. It has happened twice in ~440 pull requests, and both times the work was recoverable. Against that: 3 pull requests, 7 guard versions (4 hook + 3 detector), 3 review rounds finding 5 → 15 → 15 defects, and 5 new register concerns — none of which touched the product. This PR's own register entry contains the sentence "the guard had already cost more than the failures", written before three more attempts at the guard.

The detector is not worth what it costs to make correct. Round three found seven defects in the test guards written to prevent exactly the defect class the guards were for. That is the signal to stop, not to iterate.

What survives, in a separate minimal PR: the broken v1 pre-push hook is still on development and the guide still tells you to install it — that trap comes out. Everything else here (the detector, its guards, five new concerns, the register narrative) is discarded.

C-340 mechanism 1 remains solvedscripts/arm_automerge.sh is already on development from #437 and works. Mechanism 2 is accepted as a known nuisance: if a push lands on a merged branch, cherry-pick it onto a fresh branch.

Superseded work, for the record: #437 (merged, shipped the broken hook), #438 (closed), this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant