fix(hybrid-gate): resolve the trailer tip on push events, not just pull_request - #26
Merged
Merged
Conversation
…ll_request check-trailer read `github.event.pull_request.head.sha` unconditionally. That context is populated on pull_request events only, so on a push the variable is empty and `git log -1 --format=%b ""` exits 128. The step dies before writing its `found` output, full-gate-build never runs because its `if:` reads that output, and the gate job fails with none of its three success conditions met. Regression from the #2399 tip-binding fix, which correctly stopped an ancestor's trailer riding through to a green check but referenced a PR-only context without a push fallback. The same file already guards this exact hazard twice — the docs-only and ai-attribution steps each carry a BASE_REF -> EVENT_BEFORE -> HEAD~1 chain with a WHY comment saying every push to a default branch failed the gate. This restores that pattern in the one place it was missed. #2399's property is preserved: on a pull_request the tip is still head.sha; on a push, HEAD is the tip. Surfaced by aletheia, the only consumer whose caller adds `push: branches: [main]` alongside `pull_request:` — every other consumer triggers on pull_request only and never reaches the empty variable.
forkwright
added a commit
that referenced
this pull request
Jul 30, 2026
Resolves the check-trailer conflict against #26, which landed a different repair for the same push-event crash. #26 guards at the shell level (`tip="${PR_HEAD_SHA:-HEAD}"`); this branch guards at the expression level (`github.event.pull_request.head.sha || github.sha`). The auto-merge kept BOTH, leaving two fallbacks for one hazard and a WHY comment asserting "On a push, PR_HEAD_SHA is empty" that the surviving expression-level fallback had just made false. Keeps the expression-level form and drops the shell-level alias: - `github.sha` is the pushed commit. `HEAD` is whatever the checkout resolved to, which is the same commit only because actions/checkout put it there — a property of the preceding step, not of this one. - the tip is named by one expression the reader can evaluate against the event payload, with no intermediate to trace. - #2399's tip-binding is preserved under both event shapes, unchanged. Refs #26
forkwright
added a commit
that referenced
this pull request
Jul 30, 2026
The guard matched the env var name against git command lines directly, so
one assignment hid a site from it entirely:
tip="$PR_HEAD_SHA"
body=$(git log -1 --format="%b" "$tip")
reported clean. That is the defect class this guard exists to catch,
escaping through the shape it most often takes — hybrid-gate.yml's own
check-trailer step routed the value through exactly that alias until the
preceding commit removed it. The guard would have stayed green if the
fallback in that alias were ever dropped.
Follows assignments to a fixpoint, so an alias of an alias cannot hide the
site either.
Also treats `${NAME:-fallback}` and `${NAME:=fallback}` as guards. They
resolve the empty-string hazard as completely as `[ -n ]`, and #26's repair
used that form — a guard that reports a correct fix as a violation teaches
its readers to work around it.
Verified by discrimination rather than by a green: two workflows differing
only in `:-HEAD` were both reported clean before this change and are now
reported differently — the unguarded alias errors, the guarded one passes.
The pre-existing true positive on gate-attestation.yml's unguarded direct
use still fires, and the 15 sites in this repo's workflows still pass.
Refs #26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
check-trailerreadgithub.event.pull_request.head.shaunconditionally. That context is populated onpull_requestevents only, so on a push the variable is empty andgit log -1 --format=%b ""exits 128:The step dies before writing its
foundoutput.full-gate-build'sif:reads that output and so never runs,BUILD_RESULTisskipped, and thegatejob fails with none of its three success conditions (trailer found / docs-only / build success) satisfied.Why it is a regression, and why the fix is a restoration
This came in with the #2399 tip-binding fix, which was correct in substance — an un-stamped tip must not ride an ancestor's trailer to a green check. But it referenced a PR-only context without a push fallback.
The same file already guards this exact hazard twice. The "Check for docs-only changeset" and "ai-attribution" steps each carry a
BASE_REF → EVENT_BEFORE → HEAD~1 → HEADchain, and their WHY comments say plainly thatgithub.base_refis populated forpull_requestevents only and that every push to a default branch failed the gate before those chains existed. So this class was known, solved, documented — and reintroduced in the one step that did not reuse the pattern.#2399's property is preserved: on a
pull_requestthe tip is stillhead.sha; on a push,HEADis the tip.Why only aletheia went red
aletheia's caller is the only consumer that adds
push: branches: [main]alongsidepull_request:. theatron, harmonia, thumos and akroasis all trigger onpull_requestonly, so theircheck-trailernever reaches the empty variable. kanon and logismos do not consume this reusable at all.That is worth noting for review: the blast radius of the bug was narrow by accident, not by design. Any consumer that adds a push trigger would have hit it.
Verification
YAML re-parsed after the edit. The change is confined to one step; the resolved tip is also echoed in the success line so a future run says which commit it verified rather than leaving that to inference.
Refs #2399