Skip to content

fix(ci): derive the diff range from the event so pushes stop failing the gate - #25

Merged
forkwright merged 1 commit into
mainfrom
fix/hybrid-gate-push-event
Jul 28, 2026
Merged

fix(ci): derive the diff range from the event so pushes stop failing the gate#25
forkwright merged 1 commit into
mainfrom
fix/hybrid-gate-push-event

Conversation

@forkwright

Copy link
Copy Markdown
Owner

The failure

fatal: ambiguous argument 'origin/..HEAD': unknown revision or path not in the working tree.

github.base_ref is populated for pull_request events only. On a push it is empty, so both git-range sites in this reusable interpolated to the literal ref origin/..HEAD and the step aborted with exit 128.

Every push to a default branch has failed the gate since this reusable was adopted. aletheia's Gate Attestation has been red on main since at least 2026-07-24 — six consecutive pushes, every one a merge that landed with the gate reporting failure. It went unnoticed because pull requests populate base_ref and take a working path through the same workflow, so PR-side signal looked healthy the whole time.

The fix

Both sites now derive the range from whichever event actually fired:

  • a PR base when there is one,
  • otherwise the push's before commit,
  • otherwise the head commit alone.

before is all-zeros on a branch's first push and may be unresolvable under a shallow fetch, so it is verified with git rev-parse --verify rather than assumed. Every branch of that decision resolves to a range git will accept.

Why the attribution scan matters here

The docs-only step failing is a nuisance. The attribution scan failing is the part worth fixing carefully: a push to the default branch is exactly when an attribution marker would land without review. Making that step abort on the event it most needs to cover is the worst possible failure mode, so it keeps scanning on a push rather than being skipped.

Blast radius

This reusable is consumed by aletheia and epitelesis. The change only adds fallbacks on a path that previously always aborted, so PR behaviour is byte-identical — BASE_REF non-empty takes the same first branch it always did.

Verification

YAML parses. Neither origin/${{ github.base_ref }} site remains. The real proof is the next push to a consumer's default branch going green, which I will confirm rather than assume.

…the gate

`github.base_ref` is populated for pull_request events only. On a push it is empty, so both
git-range sites interpolated to the literal ref `origin/..HEAD` and the step died with
`fatal: ambiguous argument`. Every push to a default branch has failed the gate since this
reusable was adopted — aletheia's Gate Attestation has been red on main for days, and the
signal went unnoticed because pull requests take a different path through the same workflow.

Both sites now derive the range from whichever event fired: the PR base when there is one,
otherwise the push's `before` commit, falling back to the head commit alone. `before` is
all-zeros on a branch's first push and may be unresolvable in a shallow fetch, so it is
verified rather than assumed.

The attribution scan in particular must keep working on a push — that is precisely when a
marker would reach the default branch without review.
@forkwright
forkwright merged commit 43a64b9 into main Jul 28, 2026
1 check passed
@forkwright
forkwright deleted the fix/hybrid-gate-push-event branch July 28, 2026 15:49
forkwright added a commit that referenced this pull request Aug 15, 2026
…ot the base tip (#40)

`git diff A..B` is not range notation — it is a plain comparison of two
endpoints, identical to `git diff A B`. The docs-only step fed it
`origin/${BASE_REF}..HEAD`, so against a base that keeps moving it reported
every file main changed since the branch forked, not the files the PR
proposes. A PR touching only README.md is classified NOT docs-only the moment
an unrelated .rs commit lands on main.

Measured on forkwright/kanon's `docs/2254-entry-point-truth`, which changes 2
files: the two-dot range yields 39 files, 37 of them non-doc. Three-dot yields
2. The exemption can therefore only ever fire on a branch that is exactly
up to date with its base, which is not a property any PR holds for long.

The consequence differs by gate shape. Here the PR falls through to
full-gate-build, so the cost is a wasted build. In a trailer-only gate with no
build fallback the same expression hard-blocks the PR: kanon's standalone
gate-attestation.yml carries the identical two-dot expression at line 52 and
exits 1 with "No Gate-Passed trailer found" for a docs-only PR that its own
exemption was written to let through.

Only the pull_request arm changes. The push arms are correct as two-dot:
before..after is precisely what that push changed, and all three were
re-checked against a fixture (event.before present, all-zeros, and absent) and
resolve unchanged.

Verified by extracting this workflow's own range-selection block and running it
against a fixture PR whose branch touches only README.md while the base moved
ahead with a code change:

  pre-fix  (origin/main):  range=origin/main..HEAD   files=[README.md code.rs]  docs_only=false  rc=1
  post-fix (this branch):  range=origin/main...HEAD  files=[README.md]          docs_only=true   rc=0

The permanent assertion belongs in scripts/check_event_shape_guards.py, which
scans for pull_request-only values reaching git and would cover this class
directly. That script is still unmerged on fix/gate-push-event-trailer-sha, and
wiring it from here would collide with that branch in actionlint.yml, so this
change is scoped to the defect.

Follows up #25, which introduced the event-derived range.
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