ci: a rebase ran every path-filtered workflow, because paths: matches the push delta #191

Merged
dcharlot merged 1 commit from ci/pr-diff-path-gate into main 2026-10-09 02:48:49 -04:00
Owner

Forgejo matches a workflow's paths: filter against the push delta, not the
pull request's diff. A rebase moves a branch across every commit main has
gained, so its delta touches nearly every path — and every path-filtered
workflow in the repo fires.

Measured, as a clean natural experiment

Five PRs open right now, all changing only jcp/ or mesh/:

PRs how pushed workflows triggered
#189, #190 fresh branch, one push 2
#165, #177, #183 force-push after rebase 15

Those fifteen include JPEG-AI whole decode, wai-pack on three platforms, and
cross-architecture byte-equality on three — none of which a change to
jcp/jcp-rs/crates/jcp-grant/src/checkpoint.rs can affect.

Rebasing three PRs put the queue 65 runs deep behind one macOS runner, which
delays everyone's real jobs. And there is no way to rebase without it: merging
main into the branch has the same delta.

The fix

The paths: filters still gate the trigger. A new relevant job gates the
work, decided from the pull request's own diff.

It reads the path list out of the workflow file it guards, so there is one
list
rather than a third copy to drift from the two on: blocks. On a push to
main there is no pull-request context, the shas are empty, and the gate answers
true — main runs everything, as before.

It fails open, by construction

A gate that decides whether tests run is load-bearing in the dangerous
direction: a wrong false silently skips a whole workflow — the "passes but
tested nothing" failure. Every way the script can fail to decide prints true:

  • a missing workflow file
  • a filter it cannot parse
  • an empty base sha
  • a sha git cannot resolve
  • too few arguments
  • an empty diff

The self-test pins all six, both directions of the real decision, and that **
spans a path separator while * does not.

Writing that self-test caught one of its own cases. The single-star case
first created a top-level .md alongside the nested one, so the glob matched
legitimately and the case proved nothing. It now changes only the nested file,
and asserts separately that the glob does match at its own level — so it
cannot pass by never matching anything.

Scoped to one workflow on purpose

Applied to wai-pack.yml alone, not all eighteen.

The open question is what a skipped job reports as a commit status on this
forge.
A required context that never reports would make PRs unmergeable. That
failure is at least loud rather than silent, but it is worth learning on the
most expensive workflow — six job instances across three platforms — before
changing the rest. If skipped jobs report cleanly, rolling out to the other
seventeen is mechanical.

Validated against real branches before touching any workflow: every WAI workflow
answers false for #165's jcp-only diff and true for a WAI branch, and
standards-build.yml correctly answers true for #165 because it builds every
workspace. conformance.yml answers false — correctly, it is smart-byte's
gate.

The hygiene workflow runs the self-test, so the gate cannot regress silently,
and its own paths: filter now lists the scripts it checks.

🤖 Generated with Claude Code

Forgejo matches a workflow's `paths:` filter against the **push delta**, not the pull request's diff. A rebase moves a branch across every commit main has gained, so its delta touches nearly every path — and every path-filtered workflow in the repo fires. ## Measured, as a clean natural experiment Five PRs open right now, all changing only `jcp/` or `mesh/`: | PRs | how pushed | workflows triggered | |---|---|---| | #189, #190 | fresh branch, one push | **2** | | #165, #177, #183 | force-push after rebase | **15** | Those fifteen include JPEG-AI whole decode, `wai-pack` on three platforms, and cross-architecture byte-equality on three — none of which a change to `jcp/jcp-rs/crates/jcp-grant/src/checkpoint.rs` can affect. Rebasing three PRs put the queue **65 runs deep** behind one macOS runner, which delays everyone's real jobs. And there is no way to rebase without it: merging main into the branch has the same delta. ## The fix The `paths:` filters still gate the **trigger**. A new `relevant` job gates the **work**, decided from the pull request's own diff. It reads the path list out of the workflow file it guards, so there is **one list** rather than a third copy to drift from the two `on:` blocks. On a push to main there is no pull-request context, the shas are empty, and the gate answers `true` — main runs everything, as before. ### It fails open, by construction A gate that decides whether tests run is load-bearing in the dangerous direction: a wrong `false` silently skips a whole workflow — the "passes but tested nothing" failure. Every way the script can fail to decide prints `true`: - a missing workflow file - a filter it cannot parse - an empty base sha - a sha git cannot resolve - too few arguments - an empty diff The self-test pins all six, both directions of the real decision, and that `**` spans a path separator while `*` does not. Writing that self-test **caught one of its own cases**. The single-star case first created a top-level `.md` alongside the nested one, so the glob matched legitimately and the case proved nothing. It now changes only the nested file, and asserts separately that the glob *does* match at its own level — so it cannot pass by never matching anything. ## Scoped to one workflow on purpose Applied to `wai-pack.yml` alone, not all eighteen. **The open question is what a *skipped* job reports as a commit status on this forge.** A required context that never reports would make PRs unmergeable. That failure is at least loud rather than silent, but it is worth learning on the most expensive workflow — six job instances across three platforms — before changing the rest. If skipped jobs report cleanly, rolling out to the other seventeen is mechanical. Validated against real branches before touching any workflow: every WAI workflow answers `false` for #165's jcp-only diff and `true` for a WAI branch, and `standards-build.yml` correctly answers `true` for #165 because it builds every workspace. `conformance.yml` answers `false` — correctly, it is smart-byte's gate. The hygiene workflow runs the self-test, so the gate cannot regress silently, and its own `paths:` filter now lists the scripts it checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ci: a rebase ran every path-filtered workflow, because paths: matches the push delta
All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 1h4m33s
standards workspaces build + test / aac-rs (pull_request) Successful in 15m41s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 5m23s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 4m7s
standards workspaces build + test / map-rs (pull_request) Successful in 5m41s
standards workspaces build + test / mesh-rs (pull_request) Successful in 2m16s
standards workspaces build + test / openpay (pull_request) Successful in 13m43s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m13s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m55s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m30s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m13s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m17s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m18s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m16s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m5s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m37s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m59s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m47s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m54s
wai-pack / wai-pack · linux (pull_request) Successful in 1m48s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 3m0s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 24s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m0s
wai-pack / wai-pack · macos (pull_request) Successful in 2m11s
a544054098
Forgejo matches a workflow's `paths:` filter against the **push delta**, not the
pull request's diff. A rebase moves a branch across every commit main has
gained, so its delta touches nearly every path and every path-filtered workflow
fires.

Measured on this repo, as a clean natural experiment — five PRs, all changing
only `jcp/` or `mesh/`:

| PRs | how pushed | workflows triggered |
|---|---|---|
| #189, #190 | fresh branch, one push | **2** |
| #165, #177, #183 | force-push after rebase | **15** |

Fifteen includes JPEG-AI whole decode, `wai-pack` on three platforms, and
cross-architecture byte-equality on three — none of which a change to
`jcp/jcp-rs/crates/jcp-grant/src/checkpoint.rs` can affect. Rebasing three PRs
put the queue 65 runs deep behind one macOS runner, which delays everyone's real
jobs, and there is no way to rebase without it: a merge of main into the branch
has the same delta.

So the `paths:` filters still gate the *trigger*, and a new `relevant` job gates
the *work*, from the pull request's own diff. It reads the path list out of the
workflow file it guards, so there is one list rather than a third copy to drift
from the two `on:` blocks. On a push to main there is no pull-request context,
the shas are empty, and the gate answers `true` — main runs everything, as
before.

**It fails open, by construction.** A gate that decides whether tests run is
load-bearing in the dangerous direction: a wrong `false` silently skips a whole
workflow, which is the "passes but tested nothing" failure. Every way the script
can fail to decide — a missing workflow file, a filter it cannot parse, an empty
or unresolvable sha, too few arguments, an empty diff — prints `true`. The
self-test pins all six of those, both directions of the real decision, and that
`**` spans a path separator while `*` does not.

Writing that self-test caught one of its own cases: the single-star case first
created a top-level `.md` alongside the nested one, so the glob matched
legitimately and the case proved nothing. It now changes only the nested file,
and asserts separately that the glob does match at its own level — so it cannot
pass by never matching anything.

Applied to `wai-pack.yml` alone, not all eighteen. What a *skipped* job reports
as a commit status on this forge is the open question, and a required context
that never reports would make PRs unmergeable. That failure is at least loud
rather than silent, but it is worth learning on the most expensive workflow —
six job instances across three platforms — before changing the rest. Validated
against real branches first: every WAI workflow answers `false` for #165's
jcp-only diff and `true` for a WAI branch, and `standards-build.yml` correctly
answers `true` for #165 because it builds every workspace.

The hygiene workflow runs the self-test, so the gate cannot regress silently,
and its own `paths:` filter now lists the scripts it checks.

Co-Authored-By: Claude Opus 5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!191
No description provided.