Skip to content

fix(hybrid-gate): filter blobs from the two metadata-only checkouts - #33

Merged
forkwright merged 1 commit into
mainfrom
fix/gate-checkout-blob-filter
Aug 12, 2026
Merged

fix(hybrid-gate): filter blobs from the two metadata-only checkouts#33
forkwright merged 1 commit into
mainfrom
fix/gate-checkout-blob-filter

Conversation

@forkwright

Copy link
Copy Markdown
Owner

Finding

check-trailer and ai-attribution each run actions/checkout with fetch-depth: 0 under timeout-minutes: 5. Neither job reads file content — one reads commit trailers and changed path names, the other scans commit messages and PR metadata. On a large adopting repo the unfiltered full-history clone is effectively the entire job.

When it overruns the budget, 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 reads like a build problem.

Evidence

Measured in forkwright/aletheia today, on two unrelated PRs whose own code was fine:

run job duration cancelled at step
31621077227 check-trailer 17:07:57 → 17:13:01 (5m04s) actions/checkout
31632599095 check-trailer 19:25:48 → 19:30:50 (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 run, ai-attribution completed in 21 seconds while check-trailer sat in checkout for five minutes.

This is a different root cause from the concurrency-keying issue this workflow's existing WHY comments describe (aletheia#6701). That one cancelled whole runs via the concurrency group; this cancels a single job while its siblings succeed, which is what makes it look like a code problem.

Correction

Add filter: blob:none to those two checkouts. This keeps every commit and tree — so git log, trailer reads, and git diff --name-only are unaffected — while leaving blobs unfetched.

It is also safe against future edits: a partial clone lazily fetches any blob a step turns out to need. The change cannot break a step that reads content; it only stops paying for content nothing reads.

full-gate-build deliberately keeps its unfiltered clone — it compiles the tree.

Blast radius

This is the fleet-shared reusable gate, so it affects every adopting repo. The change is confined to two checkout steps and removes work rather than adding any; the failure mode if blob:none were wrong would be a step failing to read a file, which is loud rather than silent.

Adopting repos pin this workflow by SHA, so nothing moves until each repo bumps its pin.

`check-trailer` and `ai-attribution` both clone full history under a
5-minute budget, but neither reads file content — one reads commit
trailers and changed path names, the other scans commit messages and PR
metadata. On a large adopting repo the unfiltered clone is effectively the
whole job.

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 in aletheia: two runs cancelled at exactly 5m04s and 5m02s, both
inside this checkout step, on unrelated PRs whose own code was fine.

`blob:none` keeps every commit and tree and leaves blobs unfetched. Git
lazily fetches any blob a step turns out to need, so this cannot break a
future step that does read content — it only stops paying for content
nothing reads.

`full-gate-build` keeps its unfiltered clone: it compiles the tree.
@forkwright
forkwright merged commit 9635326 into main Aug 12, 2026
1 check passed
@forkwright
forkwright deleted the fix/gate-checkout-blob-filter branch August 12, 2026 20:01
forkwright added a commit to forkwright/aletheia that referenced this pull request Aug 13, 2026
## 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.

`aed4ca519` → `963532686f7853fa3555aab434455693969b63a1`.

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