Skip to content

fix(hybrid-gate): resolve the trailer tip on push events, not just pull_request - #26

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

fix(hybrid-gate): resolve the trailer tip on push events, not just pull_request#26
forkwright merged 1 commit into
mainfrom
fix/hybrid-gate-push-event-tip

Conversation

@forkwright

Copy link
Copy Markdown
Owner

What

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:

fatal: ambiguous argument '': unknown revision or path not in the working tree.
##[error]Process completed with exit code 128.

The step dies before writing its found output. full-gate-build's if: reads that output and so never runs, BUILD_RESULT is skipped, and the gate job 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 → HEAD chain, and their WHY comments say plainly that github.base_ref is populated for pull_request events 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_request the tip is still head.sha; on a push, HEAD is the tip.

Why only aletheia went red

aletheia's caller is the only consumer that adds push: branches: [main] alongside pull_request:. theatron, harmonia, thumos and akroasis all trigger on pull_request only, so their check-trailer never 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

…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
forkwright merged commit 84a39d3 into main Jul 30, 2026
1 check passed
@forkwright
forkwright deleted the fix/hybrid-gate-push-event-tip branch July 30, 2026 15:31
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
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