Skip to content

ci(gate): bump the hybrid-gate pin for the checkout blob filter - #6739

Merged
forkwright merged 1 commit into
mainfrom
fix/bump-hybrid-gate-blobfilter
Aug 13, 2026
Merged

ci(gate): bump the hybrid-gate pin for the checkout blob filter#6739
forkwright merged 1 commit into
mainfrom
fix/bump-hybrid-gate-blobfilter

Conversation

@forkwright

Copy link
Copy Markdown
Owner

Why

check-trailer in the shared hybrid gate clones full history under timeout-minutes: 5, but reads only commit trailers and changed path names — never file content. On a repo this size that clone is the job.

When it overruns, the job reports cancelled. The gate cannot distinguish that from a supersede, so the PR goes red with nothing wrong with it — and because full-gate-build has needs: check-trailer, the build is skipped rather than run, which presents as a build failure.

Evidence from this repo, today

run job duration cancelled at
31621077227 check-trailer 5m04s actions/checkout
31632599095 check-trailer 5m02s actions/checkout

Both land on the timeout to within seconds, both inside the checkout step, with sibling jobs in the same run succeeding — in the second, ai-attribution finished in 21 seconds while check-trailer sat in checkout for five minutes.

This kept reappearing as four different-looking failures across unrelated PRs (#6721, #6723, #6731, #6737), which is what made it read as four independent breaks rather than one infrastructure timeout.

It is a different root cause from #6701's concurrency keying: that cancelled whole runs via the concurrency group; this cancels a single job while its siblings succeed.

What the bump brings

forkwright/.github#33 adds filter: blob:none to check-trailer's and ai-attribution's checkouts. Full history and all trees are kept — so git log, trailer reads and git diff --name-only are unaffected — while blobs go unfetched.

It is safe against future edits: a partial clone lazily fetches any blob a step turns out to need, so it cannot break a step that reads content. full-gate-build keeps its unfiltered clone, since it compiles the tree.

aed4ca519963532686f7853fa3555aab434455693969b63a1.

`check-trailer` clones full history under a 5-minute budget and reads only
commit trailers and changed path names — never file content. On this repo
that clone is the whole job, and when it overruns the job reports
`cancelled`, which the gate cannot distinguish from a supersede. The PR
goes red with nothing wrong with it, and `full-gate-build` is skipped
because it needs `check-trailer`.

Measured here today: runs cancelled at exactly 5m04s and 5m02s, both
inside that checkout step, on unrelated PRs.

forkwright/.github#33 adds `filter: blob:none` to that checkout and to
ai-attribution's. Full history and trees are kept, blobs are not fetched,
and git lazily fetches any blob a step turns out to need.
@sonarqubecloud

Copy link
Copy Markdown

@forkwright
forkwright merged commit e53462e into main Aug 13, 2026
31 of 34 checks passed
@forkwright
forkwright deleted the fix/bump-hybrid-gate-blobfilter branch August 13, 2026 14:06
forkwright added a commit that referenced this pull request Aug 13, 2026
## Finding

`detect CodeQL scope` failed at **5m05s** against `timeout-minutes: 5`
on PR #6740. The dependent `codeql-pr` then failed four seconds later,
having done nothing.

This is the same defect #6739 fixed for `check-trailer`, in a workflow
that fix could not reach.

The job checks out with `fetch-depth: 0` — every commit, every tree,
**every blob** — and then does exactly this:

```
git diff --name-only "origin/${BASE_REF}...HEAD"
```

Path names. It never opens a file. The blob download is the entire cost
of the job.

## Why it is worse than a slow job

When the clone overruns the budget, the job reports **`cancelled`**, not
`failed`. GitHub uses that same status for a superseded run, so the two
are indistinguishable in the checks list — a real timeout reads as "an
older run got replaced." That is what made the `check-trailer` version
of this survive so long, and it is why `codeql-pr` looks like a CodeQL
problem when nothing about CodeQL is involved.

## Why #6739 did not cover it

#6739 bumped the pin to `forkwright/.github`'s fixed reusable workflow,
which carries the filter for `check-trailer`. aletheia's **own**
workflows were never touched, and an audit shows **none of its 30
`actions/checkout` steps across 20 workflows carried `filter:
blob:none`**.

## What changed

Two jobs qualify — both clone everything and read only git metadata:

| workflow | what it actually reads |
|---|---|
| `codeql-pr.yml` (`detect CodeQL scope`) | path names, via `git diff
--name-only` |
| `no-ai-attribution.yml` | commit messages, via `git log --format` |

Both get `filter: blob:none`, keeping the commits and trees they do
need. Each carries a `WHY(filter)` comment so a later reader does not
"tidy" it away as a stray option.

## Deliberately not changed

The filter is not free for every job, so this is not a sweep:

- **`codeql-pr.yml`'s second checkout** feeds CodeQL's analyzer, which
reads sources. It needs blobs.
- **`gate-attestation.yml`** reads commit trailers *and* builds, across
three separate checkouts. Those need judging one at a time, not as a
class.
- Every build/test job legitimately needs the full tree — a lazy blob
fetch during a compile is slower, not faster.

Filed as **#6757** so the remaining 28 get triaged deliberately rather
than blanket-patched.

## Verification

Both files parse as valid YAML (`yaml.safe_load`). The real proof is the
next run of `detect CodeQL scope`: it should drop from ~5 minutes to
seconds, the way `check-trailer` went from a 5m02s timeout-cancel to **7
seconds** after the equivalent fix.

---

## Third instance, caught live

`attestation-verify` was **cancelled at 5m04s** on PR #6749 while this
PR was open. The gate's own evaluation says it outright:

```
ATTESTATION_VERIFY_RESULT: cancelled
CANCELLED: attestation-verify did not run to a verdict (result: cancelled).
This is NOT a check failure: re-run the job.
```

So a PR with nothing wrong with it reads as gate-failed, and the only
remedy is knowing to re-run.

That job reads a commit message, one `kanon.toml`, and a tree hash. **It
never builds.** Its entire 5-minute budget was going to blobs it does
not open.

I had deferred all of `gate-attestation.yml` from this PR on the grounds
that the workflow "reads trailers *and* builds". That was too coarse a
judgment: the file has three checkouts and they are not alike.
`attestation-verify` is metadata-only and is now filtered. The other two
are untouched — `gate-coverage-scripts` runs `cargo metadata` and
scripts across the tree, and genuinely wants blobs.

Three demonstrated instances now — `check-trailer`, `detect CodeQL
scope`, `attestation-verify` — each found only by reading a job log
after something else looked broken. That is the argument for #6757 doing
the remaining triage systematically rather than waiting for each one to
bite.

---------

Co-authored-by: Cody Kickertz <admin@forkwright>
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