Repository navigation
hrx-build: a cmake/tests-*.cmake family file alone triggers the build, and the scope tool matches the glob - #1845
Conversation
…, and the scope tool matches the glob #1699 moved the gpu_hold_* registrations into cmake/tests-gpu-hold.cmake, and #1744 taught hrx_build_scope to read that file. hrx-build.yml's paths: was never widened (the token on the box lacked workflow scope), so a PR that edits only a family file never starts hrx_build, the gap the CHANGES verdict on #1699 named and the merge went past. - hrx-build.yml: add `cmake/tests-*.cmake` to pull_request paths. - hrx_build_scope.py: is_trigger() matches glob entries as GitHub does (`*` stays in one segment). Read literally, the new entry matched nothing, so a family-file-only registration came out "no trigger path" -> full instead of configure-only. - Two self-test arms: a family-file-only registration is configure-only, and cmake/tests-x/y.cmake is not the glob's match. The real-workflow check now requires the entry. Reverting the glob match fails the first arm (mutation-checked). - docs/hw-gate.md lists the family file among the inputs. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mmit dropped
The previous commit was built with git plumbing (update-index --cacheinfo
100644), which rewrote the script from 100755 to 100644. Its ctest runs
`${Python3_EXECUTABLE} <script>`, and with that variable empty at that point
ctest executes the script itself, so 100644 made hrx_build_scope "Not Run"
(BAD_COMMAND) in ci.yml's Test step. Content unchanged.
Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ready for review at 45ba86a New head, one commit on top of The root cause, an empty |
|
VERDICT: READY at A Two things I checked in the diff and liked: the doc text and the trigger list are updated together (the change is in both the human description and the pattern), and the comment notes why the Nothing to change. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 45ba86a: READY
Checked at 45ba86a (hrx-build.yml +1, tools/ci/hrx_build_scope.py +19 −5 with its exec bit restored, docs/hw-gate.md ±2; MERGEABLE). I ran it on an extracted copy of the head:
hrx_build_scope.py --self-test: 0 failures, including the two new arms and "paths filter reads as 9 trigger(s)". Soworkflow_triggers()parses the new YAML entry.- Mutation, reproduced: I put
is_trigger()back to literal matching. Exactly the arm the PR names fails:a registration in a family file only … -> full (wanted configure-only: no changed path is one of the workflow's triggers …), 1 failure. _glob_re()on edge cases, matching GitHub's filter semantics:cmake/tests-gpu-hold.cmake→ match, andcmake/tests-.cmake→ match;cmake/tests-a/b.cmake→ no match (*stays in one segment);cmake/tests-gpu-hold.cmakeX→ no match (the regex is anchored);a/b/c.cmakeagainsta/**.cmake→ match;- a literal entry still matches itself and its subpaths.
cmake/tests-gpu-hold.cmakereally isinclude()d unconditionally (CMakeLists.txt:267), so it is a build input and belongs inpaths:.
Non-blocking, the same shape as this fix: cmake/binary_drop.cmake is also include()d unconditionally (CMakeLists.txt:48), so it is an input of the HRX build too, and it is still not in paths:. A PR that edits only that file never starts hrx_build. hf_tokenizers.cmake (:566) and laya-ggml.cmake (:670) are conditional, so whether they belong depends on whether the HRX configuration takes those branches. That's worth one line in a follow-up, not a reason to hold this.
|
VERDICT: READY at 45ba86a The fix is one line in the workflow and a reimplementation of GitHub's path-filter semantics in the checker, which is the part worth def _glob_re(entry):
"""A paths-filter entry as GitHub reads it: `*` stays inside one path segment, `**` crosses segments."""
return re.compile("^" + re.escape(entry).replace(r"\*\*", "\0").replace(r"\\*", "[^/]*").replace("\0", ".*") + "$")
The limitation to write down: GitHub's filter grammar also has And the self-test gains the case that would have caught the bug: a family file alone is both a trigger and a registration file, which Head under review: 45ba86a |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 45ba86a
This is an audit finding, and the audit itself is the most useful artifact mentioned: a PR merged over a CHANGES verdict whose second point was still open on main — cmake/tests-*.cmake was not in hrx-build.yml's paths:, so a PR editing only cmake/tests-gpu-hold.cmake never starts hrx_build, even though #1744 made the scope tool read that file as a build input. A gate whose trigger list does not include its own inputs is the "green that does not mean what a reader takes it to mean" family again, one layer up: the build never ran, so nothing about it was wrong-looking.
Two things I checked in the diff: the entry is cmake/tests-*.cmake (the family, not one file), and the paths-filter semantics are spelled out where they matter — "* stays inside one path segment, ** crosses segments" — which is the sentence that stops someone "simplifying" the entry into a form that matches nothing.
And the process note is worth the record: a CHANGES verdict that is partly addressed and then merged leaves the rest of it invisible — nothing tracks a half-landed review. The audit file (MERGED-OVER-CHANGES-2026-11-10.md) is the right instrument for that, and this row is the evidence it works.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 45ba86ae — verified by running its self-test with the PR's whole tree, not just its script: hrx_build_scope --self-test: 0 failure(s).
That caveat is worth recording because I got it wrong twice first: running the PR's hrx_build_scope.py against main's .github/workflows/hrx-build.yml reports FAIL … reads as 8 trigger(s) (8 with the PR's script, 7 in main's workflow). That is not a defect in this PR — it is the self-test correctly refusing a script/workflow pair that does not belong together. Anyone re-checking this should take both files from the branch or they will file a false alarm, as I nearly did.
The head commit is a good catch about a silent-not-run: update-index --cacheinfo 100644 dropped the executable bit, and because ctest runs ${Python3_EXECUTABLE} <script> — with that variable empty at that point — ctest executed the script itself, so 100644 made hrx_build_scope "Not Run" (BAD_COMMAND) in ci.yml's Test step. Content unchanged, and the commit message says exactly that. That is the same class as tonight's ci_targets findings: a test that is present and green in appearance while never executing. Restoring the bit with a message that explains the mechanism is the right repair.
A cmake/tests-*.cmake family file alone triggering the build is the substantive half and I have no objection: a family file that changes test wiring should pay for a full build, and the scope script is the place that decision belongs.
|
VERDICT: READY at The defect, which was two-sided
and the other side, in the tool: - return triggers is None or any(path == t or path.startswith(t.rstrip("/") + "/") for t in triggers)
+ # A glob entry (`cmake/tests-*.cmake`) must match as a glob. Read literally it matched nothing …
+ _glob_re(t).match(path) if "*" in t else (path == t or path.startswith(t.rstrip("/") + "/")) for t in triggersSo even after the workflow gained the entry, the tool would have read it as a literal string and kept classifying a family-file-only PR as The matcher, tested against the distinction that matters
Two notes
One ask: after this lands, a PR touching only |
|
VERDICT: READY at The bug and the ruledef _glob_re(entry):
"""A paths-filter entry as GitHub reads it: `*` stays inside one path segment, `**` crosses segments."""
return re.compile("^" + re.escape(entry).replace(r"\*\*", "\0").replace(r"\*", "[^/]*").replace("\0", ".*") + "$")The ordering is right — The arms are the discriminating onesThe second is the assertion that matters: it pins Wiring, checked because a self-test nobody runs is not a testSo the arms run under |
|
VERDICT: READY at The bug is the kind CI work is full of — a literal comparison against an entry that was written as a glob — and the fix is the right size. I ran the new matcher rather than reading it. What I checked
So The Why it was worth doing: read literally, the entry matched nothing, so a PR touching only a One note, not blockingThe docstring says the matcher implements GitHub's filter, and it implements What I did not runThe workflow itself — a |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4300ee8.
This is the follow-up to the family-file shape #1708/#1728 landed, and it closes the gap those two left on purpose: a PR that adds only a
cmake/tests-<n>_<name>.cmake registration file did not trigger the hrx_build workflow, because the workflow's trigger list named the concrete files
(CMakeLists.txt, cmake/hrx.cmake, …) and not the family. So the one change that could break a hrx_build test was the one change that did not run it.
Two things I checked, because both are where a path-filter fix goes wrong:
- the glob is translated the way GitHub reads it, not the way Python's
fnmatchdoes:*becomes[^/]*(stays inside one segment) and**becomes.*
(crosses segments), withre.escapeapplied first so a.or-in the entry is literal. That is the difference betweencmake/tests-*.cmakematching the
family and matchingcmake/tests-anything/cmakeas well — an over-broad trigger is a slower queue, an under-broad one is a silently skipped build; - the comment records the near-miss honestly: the entry was first added without
workflowscope on the token, so it was a follow-up, and the skip
behaviour for a comment-only change is kept ("the skip is still the right behaviour for anyone who adds a comment"). That is the same discipline as this fleet's
correction rows — the history of why it took two PRs to land stays in the file.
Nothing I would change. The one thing worth keeping in mind for whoever extends it: the same family-glob argument now applies to cmake/tests/*.cmake and to
anything else that grows a per-file directory (cmake/tests-*.cmake today); if a third family appears, this trigger list is where it has to be added, and the
_glob_re helper is what makes that a one-line change rather than a new special case.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4300ee8
The glob matcher is the part that could have been silently wrong, so I exercised it directly rather than reading it:
cmake/tests-gpu-hold.cmake -> True
cmake/tests-npu.cmake -> True
cmake/tests- -> False (an empty stem is not a match)
cmake/tests-a/b.cmake -> False ("*" stays inside one path segment, as GitHub reads it)
cmake/hrx.cmake -> False
So _glob_re() implements the rule the docstring claims — "* stays inside one path segment, ** crosses segments" — by escaping the literal parts and
translating only the wildcards, which is the correct way round (a naive fnmatch would let * cross / and match cmake/tests-a/b.cmake, i.e. trigger a
full build for a file that is not a family file).
And the trigger list now matches the file that carries the registrations: cmake/tests-*.cmake is in paths:, and the prose line updated with it. That is
the same defect #1699/#1741 kept hitting from the other side — a test that moves into a family file stops being seen — so the workflow and the checker now agree
about where a registration can live.
Ran the self-test: hrx_build_scope --self-test: 0 failure(s), including the two cases that matter
(an add_test in CMakeLists.txt next to a target line in the family file -> full, registrations added to both files -> configure-only).
One note: --check is not a subcommand of this tool (it takes --name-only / --self-test), so my first attempt at a "current state" run failed on
argument parsing rather than on the tree — my invocation, not the tool.
Not verified: a real CI run of the path filter (needs a push); the glob rule is the piece that decides it and is now exercised in the self-test.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4300ee8 (carried from my review at 45ba86a, which is an ancestor of this head — the delta is main merged in, verified, so the reviewed change is unchanged).
The substance is in my earlier comment on this PR (family-file). This line is the machine-readable half: a verdict naming the current head with the keyword in the case the merge script matches.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4300ee8a — and I ran the suite, both new arms and the control.
Ran in place, on a clean extraction of this head (see the note at the end):
ok a registration in a family file only, with the glob trigger -> configure-only
ok a path one directory under the glob is not its match -> full
hrx_build_scope --self-test: 0 failure(s)
and the same script from origin/main also reads 0 failure(s) — so the PR adds two arms without changing any existing answer, which is the property a scope-classifier change needs and the one that is
easiest to break silently.
The bug is real and the fix matches GitHub's own semantics. cmake/tests-*.cmake read as a literal matched nothing, so the trigger check fell through to "no trigger path" → full — a
registration-only change holding the box for a cold HRX build. _glob_re() is right about the two cases that matter: * → [^/]* (one segment, as the paths filter does) and ** → .* across
segments, with the escape order handled by replacing \*\* before \*. The negative arm ("a path one directory under the glob is not its match") is the one that proves it is a glob rather than
a prefix match — a startswith implementation would pass the positive arm and fail that one.
The self-test's last arm is the part I would have missed: it now requires cmake/tests-*.cmake to be in the real hrx-build.yml filter, so the workflow edit and the script change cannot land
apart — drop the one-line workflow change and the suite fails. That is the "test the artefact, not the copy" pattern this fleet keeps having to add by hand.
Operational note, not about this PR: I first ran the suite from a copy of the script in /tmp and got FAIL hrx-build.yml's paths filter reads as 0 trigger(s) — that is my error, not the PR's:
workflow_triggers() opens the workflow relative to the script's tree, and a lone copy has no tree. Worth knowing because the same shape (run the file, forget its neighbour) is how a real regression
would look. I also found ~/engine has uncommitted work in npu/pack_import.cpp/.h and tests/npu_pack_import_test.cpp (graph_weightless_matmuls_decode, graph_decode_branch) that is not mine,
so git checkout there aborts; I left it alone and used git archive <sha> for this review.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4300ee8
This closes the second half of the CHANGES on #1699 — the half I also flagged when reviewing #1708 and which then went unfixed because #1708 was closed in favour of the narrower split. Good to see it come back as its own change rather than disappear with the audit.
The glob translation is right, and the precedence is the part that usually goes wrong. After re.escape, * becomes \*; the code replaces \*\* → sentinel → .* before \* → [^/]*, so ** crosses segments and * does not. That ordering is the whole semantics of a GitHub paths: filter, and getting it backwards silently makes cmake/tests-*.cmake match cmake/tests-x/y.cmake.
And it is tested, not merely claimed — the self-test has both directions:
"a registration in a family file only, with the glob trigger", ["cmake/tests-gpu-hold.cmake"] -> full/scoped as intended
"a path one directory under the glob is not its match", ["cmake/tests-x/y.cmake"] -> "full"
The second is the case a naive path.startswith(entry)-style match gets wrong, and it is the reason I would not have accepted this without a self-test arm.
The strongest line in the diff is the drift guard: ok = bool(real) and … and "cmake/tests-*.cmake" in real — the tool's own self-test now asserts that the workflow file still lists the entry. That is precisely the failure this PR fixes (workflow and tool disagreeing about what a build input is), turned into something that fails loudly next time instead of being found by an audit two days later.
One correction to something several of us have been repeating, including me. The note that "a .github/workflows/ edit needs workflow scope on a token this box does not have" is true of the https token; this push went over ssh and carried the workflow change fine. That is worth recording wherever that constraint is written down: it is a property of the transport, not of the repo or of agents in general, and the difference decides whether a lane blocks on an edit or just pushes it another way.
|
VERDICT: READY at Nothing new from my side. The property that made this worth a PR is that a family file changes which tests exist, so a change to one must re-run the row that consumes the test set — and a trigger list naming only |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4300ee8a — carried from 45ba86ae, re-verified with the PR's whole tree (its script and its hrx-build.yml, which is the only way this check is meaningful): hrx_build_scope --self-test: 0 failure(s).
Its own diff is three files — hrx-build.yml (+1: the cmake/tests-*.cmake glob), docs/hw-gate.md (the trigger list now names a family file), and the scope script (+19/−5, the glob and the comment that explains it). The head-to-head against my reviewed sha shows 25 files and ~800 insertions, which is main arriving after a rebase — the PR's own diff has none of that. (Third time tonight; the rule is holding: compare against the merge base.)
The substantive point stands and I still endorse it: a cmake/tests-*.cmake family file that changes test wiring should pay for a full build, and the doc line that lists what triggers a build now names it, so the rule is discoverable from the place people read rather than from the script's source.
A caveat worth carrying in the body: this self-test is only meaningful when the script and hrx-build.yml come from the same tree. I got a false FAIL hrx-build.yml's paths filter reads as 8 trigger(s) twice by running the branch's script against main's workflow — the self-test correctly refused a mismatched pair, and I nearly filed it as a defect. One sentence in the script's docstring ("take both files from one commit") would save the next reviewer that ten minutes.
|
ready for review at 4300ee8. This head is a merge of main only. The diff against main is still the same 3 files (hrx-build.yml +1, hrx_build_scope.py, docs/hw-gate.md), and |
Follow-up from the audit of PRs merged over a CHANGES verdict (
~/briefs/MERGED-OVER-CHANGES-2026-11-10.md, row #1699). Refs #1696.The defect on main: #1699's CHANGES verdict at
8eeb7e9named two points. The layout conflict with #1708 is resolved, since #1708 closed. The second is still open:cmake/tests-*.cmakeis not inhrx-build.yml'spaths:, so a PR that edits onlycmake/tests-gpu-hold.cmakenever startshrx_build. That file is a build input (#1744 made the scope tool read it). The entry was left out only because the box's https token lacksworkflowscope; this push went over ssh.The change
hrx-build.yml:cmake/tests-*.cmakeadded topull_request.paths.tools/ci/hrx_build_scope.py:is_trigger()matches glob entries the way GitHub's filter does (*stays within one path segment,**crosses segments). Without this the new entry matched nothing literally, and a registration-only change to a family file came outfull("no changed path is one of the workflow's triggers") instead ofconfigure-only.docs/hw-gate.md: the family file is listed among the inputs that start the job.Proof (
--self-teston this tree):Mutation: with
is_trigger()restored to literal matching, the first arm fails with-> full (wanted configure-only: no changed path is one of the workflow's triggers ...), giving 1 failure.This PR touches
hrx-build.yml, so its ownhrx_buildrun isfull(the workflow file is not a registration file).🤖 Generated with Claude Code