QVAC-19792 fix: absent-package selection, cpp-lint overlay scope, carved-out TS checks, embed artifact name - #4555
Conversation
… trusted ref on-pr-nx checks out the trusted ref, never the PR, so a package the PR adds is not on disk. Handed a changed packages/<new>/package.json for a project it cannot resolve, nx fails open and marks every project affected. PR #4491 adds packages/sdk-kotlin and selected all 14, which made on-pr-nx await six ts-checks that on-pr-ts-nx correctly never published, so all six hit the 25 minute timeout. Drop those paths before handing the list to nx. Nothing is lost: a package absent from the trusted ref has no options.ci there either, so it could never have produced a matrix row. One notice per package names the remedy. Verified against #4491's 135 paths with main checked out: 135 paths in, 10 kept, sdk-kotlin dropped, affected goes from 14 to []. Matches what on-pr-ts-nx computes on the merged tree, where the package does exist.
Review StatusCurrent Status: ✅ APPROVED |
License compliance — cleanNo new dependency license findings in this PR. Warn-only (shadow) mode — this check does not block merges yet. Updated automatically by the canonical license compliance workflow. NOTICE presence (advisory)Missing NOTICE (advisory, does not block):
|
Workflow security (shadow mode)zizmor found 1102 finding(s) in Findings are annotated inline on the changed files and listed in the job summary. Reproduce locally: pipx run zizmor==1.27.0 --offline .github/ |
|
The changed-files filter in // nx@23.1.0 project-glob-changes.js — glob is **/package.json, any depth
if (isProjectFile && !existsSync(join(workspaceRoot, touchedFile.file))) {
return Object.keys(projectGraphNodes)
}Those agree only when the missing file is exactly Repro. Two real cases, each a path the filter leaves in the list:
Feed either to Fix. Keep the directory drop, then also drop surviving config paths that aren't on disk: while IFS= read -r f; do
case "$f" in
package.json|project.json|*/package.json|*/project.json)
[ -f "$f" ] || continue ;;
esac
printf '%s\n' "$f"
done < "$filtered" > "$filtered.tmp"
mv "$filtered.tmp" "$filtered" |
🎯 What problem does this PR solve?
Four independent
on-prfailures. The first was this PR's original scope; the other three surfaced on #4390 and are latentmainbugs that PR is the first to trip.on-pr-nxselect every package and fail at the 25-minute await timeoutcpp-lint (llm-llamacpp)fails atOverlay PR @qvac/fabric prebuildscpplintrow, not only the npm fabric consumersAwait PR-head TypeScript checksburns 25 minutes on the VLA and Classification workflowsintegration-testsfails atDownload prebuilds bundleprebuilds; the run publishesprebuilds-embed-llamacpp1. Package absent from the trusted ref
First hit on #4491, which adds
packages/sdk-kotlin. Same commit, two workflows, opposite answers:on-pr-nx(pull_request_target)2924f1eon-pr-ts-nx(pull_request)[]on-pr-nxchecks out the trusted ref by design, so a package the PR adds is not on disk. Handed a changedpackages/sdk-kotlin/package.jsonfor a project it cannot resolve, nx fails open and marks every project affected.on-pr-ts-nxreads the merged tree where the package exists, resolves it, sees noon-prtarget, and correctly selects nothing.2. cpp-lint applies the npm overlay to a vcpkg consumer
llm-llamacpptakesqvac-fabricthrough the vcpkg port, not npm, so it has nonode_modules/@qvac/fabric.overlay-local-fabrichard-exits when it finds none, which is correct; it is being called where it should never run. Same lane, same run:The four that passed are in
.github/fabric-consumers.json. The one that failed is not.3. Carved-out packages get no TS check produced
#4497 replaced the eight per-package producers with
on-pr-ts-nx.yml, whose static jobs gate ontspackages. That is derived fromsteps.compute.outputs.matrix, whichnx-project-matrixpublishes after stripping everycarveOut: truerow.vla-ggmlandclassification-ggmlare carve-outs that declare TS checks, so they never reachtspackagesand their producer jobs skip.A skipped caller never expands the reusable, so GitHub publishes the check under the caller job name alone, with no
/ ts-checkssuffix.on-pr-vla.ymlandon-pr-classification-ggml.ymlawait the suffixed name unconditionally, and that name cannot exist.The conceptual error is treating
carveOutas if it scoped TS checks. It says which workflow drives a package's native lanes; TS checks are package-scoped and orthogonal.4. Renamed artifact
integration-test-embed-llamacpp.ymlstill downloads the bareprebuildsname from before the per-package rename in #3903 and #4362. A sweep of everyintegration-test-*.ymlshows it was the last one.📝 How does it solve it?
Drop changed paths under a package this checkout has no directory for, before handing the list to nx. Nothing is lost: a package absent from the trusted ref has no
options.cithere either, so the matrix could never have built a row for it. This replaces "unknown project, therefore everything" with "unknown project, therefore nothing".Gate the
cpp-lintoverlay onfabric-consumers, the patterncpp-tests-nx.ymlalready uses at its own call sites. Whenllm-llamacppfinishes migrating to the npm fabric package, adding it tofabric-consumers.jsonturns the overlay on with no workflow edit.Add an
allrowsoutput tonx-project-matrix, emitted before the carve-out partition, and read it in the TS producer's filter.matrix,anyandcarveoutsare unchanged, so the native lanes still exclude carve-outs.Download
prebuilds-embed-llamacpp.|| trueon eachgrepin fix 1 is load-bearing:grepexits 1 when it prints nothing, and underset -ethat aborts the step. Both cases are normal, covered by B and C below.🧪 How was it tested?
Fix 1. Ran nx directly against #4491's real 135-path list with
mainchecked out, reproducing the CI job:14 packages, matches CI exactlypackages/sdk-kotlin/package.jsonalone14pnpm-workspace.yamlremoved14, not the triggersdk-kotlinpaths dropped[]Then the filter itself, verbatim from the action, across four diff shapes:
[][][]["@qvac/ocr-ggml"]D is the one that matters for everyone else: normal PRs are unaffected. Also confirmed the two front-ends are not the cause. On the PR head tree
--base/--headand--stdinboth return[]; on the base tree both return 14. The variable is the tree, not the file list.Fix 3. Verified in CI on probe PR #4573, whose base carries this branch. The matrix job selected both packages the probe touches, and the two consumers of that computation correctly diverge:
Matrix (on-pr)still excludingvla-ggmlis the regression guard:allrowsmust not widen the generic matrix, or the carve-out's native jobs would run twice.vla-pr-head-ts-checks / ts-checksthen ran and passed, and the job it unblocks,Await PR-head TypeScript checks / await-ts-checksin the VLA workflow, completed in 91 seconds where it previously failed at its 25-minute timeout.The same run shows the naming rule behind the original failure. Executed producers publish as
<job id> / ts-checks; skipped ones publish the bare<job id>, which is the name no awaiter can match.The
carveOutandhasTsChecksintersection across everypackages/*/project.jsonis exactly two packages:fabricis a carve-out with no TS checks, so it produces no spurious entry, andon-pr-ts-nx.ymlhas no fabric job in any case. Ran the filter against both inputs:allrowsyields["classification-ggml","ocr-ggml","vla-ggml"],matrixyields["ocr-ggml"].Fix 4. Static plus a reproduction of the failure.
name: prebuildsappears nowhere else under.github/workflows/, and the producer emitsprebuilds-${{ steps.pkg.outputs.name }}.The fix itself cannot be exercised before it lands. A relative
uses: ./.github/workflows/...resolves from the repository's default branch, not from the caller's ref, so no choice of PR base reaches the patched copy. Probe PR #4573 confirms this directly: its Embed run reportsfor every reusable it called,
058d07ef6being main's tip at the time. That run reproduces the bug verbatim,Unable to download artifact(s): Artifact not found for name: prebuilds, while the branch under review carriesprebuilds-embed-llamacppon both its base and head.The same mechanism is why this has to land here rather than ride in #4390: the PR-head copy of a relative reusable never executes.
The workflow_dispatch route is also closed, since the download step is
if: ${{ !inputs.prebuild_package }}andprebuild_packageis required on dispatch, so a manual run always takes the npm-package path and skips the step. It reaches the artifact path only throughworkflow_call.actionlint -shellcheck=clean.ts-check-names4/0,ci-trust-policy74/0,prebuild-status19/0.What this does not do
A brand-new package still gets no nx CI in the PR that adds it, because its
project.jsonis not on the trusted ref foron-pr-nxto readoptions.cifrom. That is inherent to reading config from a trusted ref and is unchanged here. This PR only stops the absence from selecting everything else. To have a new package tested in its introducing PR, land itsproject.jsonon the base branch first.