fix(ci): audit both pipelines for false positives, false negatives, warnings and errors - #302
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: #301
The JS pipeline's Release job failed on 2026-09-01 (run 33551125228) with
`Error updating npm: $ is not a function`.
Node.js 23+ adds a synthetic 'module.exports' named export to CommonJS
namespaces. use-m@8.15.0 treats that marker as a real named export and stops
unwrapping the callable CommonJS default, so once command-stream@0.19.0
(2026-08-11) added a CommonJS entry point, `const { $ } = await
use('command-stream')` resolved to undefined on the Node 24 runners.
scripts/use-module.mjs now loads use-m once and normalizes the namespace, so
all nine release scripts keep working on Node 20 and Node 24 alike. Set
CI_SCRIPTS_DEBUG=1 to print the resolved module shape (off by default).
Upstream: link-foundation/use-m#72
Refs #301
Nothing checked the workflow files themselves, so shell bugs inside `run:`
blocks and unsafe action usage only surfaced as production CI failures.
Adds .github/workflows/workflows.yml (actionlint with shellcheck + zizmor)
and .github/zizmor.yml, mirroring the js/rust pipeline templates, then fixes
all 23 actionlint/shellcheck findings and all 12 zizmor findings:
- rust.yml interpolated `${{ github.base_ref }}` straight into a `run:`
block (zizmor template-injection); it now arrives through env.
- 11 third-party actions were referenced by mutable tag; oven-sh/setup-bun,
dtolnay/rust-toolchain and peter-evans/create-pull-request are now
hash-pinned.
- `bun add "$TARBALL" 2>&1 | tee install.log` reported tee's exit status
because bash -e has no pipefail: a failed clean install left the job green.
- `npx prettier --write ".changeset/*.md" || true" swallowed a real
formatting failure, producing changeset PRs that then failed format:check.
- Unquoted $GITHUB_OUTPUT / $GITHUB_STEP_SUMMARY and `grep | wc -l` (SC2086,
SC2126, SC2129, SC2015).
- Bot commits used the unattributed `github-actions[bot]@…` email; they now
use the `41898282+` prefix so rulesets do not demand extra approval.
Refs #301
scripts/setup-npm.mjs ran a bare `npm install -g npm@latest` and never checked the result: - `@latest` is unpinned. The last green Release job (run 31466957921) silently moved the runner from npm 11.16.0 to npm 12.0.2, a major version the publish steps were never tested against. - A failed install left the job on whatever npm the runner shipped. Node.js 22.22.2 on ubuntu-24.04 ships a broken npm 10.9.7 (actions/runner-images#13883), which is below the 11.5.1 OIDC trusted-publishing minimum, so the publish step failed later with an opaque registry error. Adopts the pipeline template's approach: pin npm@11, gate on the Node.js floor, fall back through four install strategies, and assert the resulting npm version supports OIDC before the release continues. The version helpers are pure and exported, and js/tests/setup-npm.js covers them. Also attributes bot commits in simulate-fresh-merge.sh, version-and-commit.mjs and rust-version-and-commit.mjs with the `41898282+` email prefix. Refs #301
…ters The concurrency policy test only looked for a workflow-level `concurrency:` block, so the new check-only workflow - which follows the template shape of one group per job - was reported as unprotected while a job-level `cancel-in-progress: true` on a release job went unnoticed. Encode principle 10 of CI-CD-BEST-PRACTICES.md instead: a workflow is protected when it declares one group for the run or one group per job, and only a `check-` group may cancel a job in progress.
`npm audit` reported 53 advisories (12 high) against the committed lockfile and nothing in CI looked at it, so the count could only grow. - `@actions/core` and `@actions/github` were declared as runtime dependencies but imported nowhere in the repository. They were also the only packages that actually loaded `undici`, and they pulled in the Octokit 5 tree next to the Octokit 7 one this code really uses. - `npm audit fix --package-lock-only` for the non-breaking advisories. - `overrides.undici`: `@ai-sdk/provider-utils@3.0.36` declares `undici` `^5.29.0` but never imports it - the only mention in the published package is a code comment - while 5.29.0 is the last 5.x and carries three high severity WebSocket advisories with no fix inside the declared range. The override moves the (unloaded) package to a patched 7.x so the advisory leaves the dependency tree. - `allowScripts`: npm 11.17 skips install scripts unless they are allowed, and printed `npm warn allow-scripts` for @parcel/watcher and tree-sitter-bash, which means their native builds were silently skipped. Result: 36 advisories, 0 high, 0 critical.
Two ways the pipeline stayed green while something was wrong: - Nothing scanned dependencies or code for known vulnerabilities. The new Security workflow runs CodeQL (JavaScript/TypeScript, Rust and the workflow files themselves), dependency review on pull requests, `npm audit` and `cargo audit` against the committed lockfiles, plus a weekly schedule so an advisory published after a merge still surfaces. - GitHub reports a job killed by `timeout-minutes` as cancelled, not failed, and every release gate is written as `needs.<job>.result == 'success'`. A timed-out check therefore skipped the release and left the run green. The new pipeline-status job observes every job and turns that into a failure on main, with scripts/check-pipeline-status.sh covered by unit tests.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
`cargo audit` on the committed Cargo.lock reports h2 0.4.15 as vulnerable to unbounded empty DATA frames (RUSTSEC-2026-0258, fixed in 0.4.16). Nothing in the pipeline looked at the Rust lockfile before the Security workflow added in this branch, so the advisory had been sitting there unnoticed. `cargo update -p h2 --precise 0.4.19` also re-resolved four crates from windows-sys 0.61.2 down to 0.52.0; those references are kept as they were, so this commit changes h2 and nothing else. Verified with `cargo check --locked`.
Two warning sources showed up in every CI run (issue #301): - The AI SDK warned "System messages in the prompt or messages fields can be a security risk because they may enable prompt injection attacks. Use the system option instead when possible." for both src/session/summary.ts (system entries inside messages) and src/agent/agent.ts (system entries inside prompt). Both now pass the system prompts through the system option. - js/tests/simulate-fresh-merge.js committed LF fixtures in throwaway repositories, so the windows-latest matrix leg printed six "LF will be replaced by CRLF" warnings per run. The throwaway repositories now set core.autocrlf=false. tests/ai-sdk-warnings.ts gains regression coverage for both halves: the old shape still warns, the new system option shape does not.
Two best practices from the pipeline templates that this repository was missing (issue #301): - scripts/run-with-budget-warning.sh gives every long step its own deadline. A job killed by timeout-minutes is reported as *cancelled*, not failed, so an overrun could pass unnoticed; a step that owns its budget warns at 70% and exits 124 with a titled error instead. Wired into the Bun and cargo suites, the cargo release build, the release dependency install and both publish steps. - Concurrency moves from one workflow-level group to per-job groups: read-only checks use check-<workflow>-<ref>-<job> and cancel in progress off main, writers share main-writer-<repo>-main and never cancel. A slow release no longer queues the next commit's checks, and the JS and Rust releases still cannot race each other. Applied to all five workflows, not only js.yml.
Both link-foundation pipeline templates run lychee over the repository and fall back to the Wayback Machine before failing; this repository had no link checking at all, and a local lychee run over the tree found 50 errors. - .github/workflows/links.yml: lychee via lycheeverse/lychee-action@v2 with `fail: false`, a job summary, and a final loud failure step, gated by the per-job concurrency groups from CI-CD-BEST-PRACTICES principle 10. - scripts/check-web-archive.mjs (+ fixture and js/tests/check-web-archive.js, ported to bun:test): resolves reported errors against the Wayback Machine so archived-but-dead links do not fail the run, while non-HTTP errors (missing files, unresolvable root-relative links) still do. - TOOLS.md, docs/groq.md, docs/openrouter.md: fix the real link rot the run exposed - 13 test links pointing at the pre-rename tests/*.tools.test.js paths, plus four moved groq/openrouter documentation URLs. - .lycheeignore: template list plus claude.ai (403 for bots) and api.kilo.ai (an API base URL that 404s on GET). - .prettierignore (both copies): keep the fixture byte-faithful to lychee's output, which uses `*` bullets prettier would rewrite.
Collect the CI/CD logs, workflow metadata, template snapshots and research notes behind issue #301 into dev/log/issues/301/pulls/302/, together with the analysis derived from them: the reconstructed timeline, every requirement of the issue enumerated, the root cause of each finding, the solution options considered, the off-the-shelf components surveyed, and the findings left open with their reasoning.
js/.changeset already carried an unreleased entry and scripts/validate-changeset.mjs allows exactly one changeset per pull request, so this change is appended to it rather than added as a second file.
actions/dependency-review-action exits with "Dependency review is not supported on this repository" when the repository's dependency graph is disabled, so the Security workflow reported a failure on every pull request for a settings reason rather than a dependency problem. The job now probes the dependency-graph API first and skips the review with a warning on 403/404 while still failing on any other unexpected status - and on real advisories.
Working session summaryDone. PR #302 is out of draft with all five workflows green: #302 Reported failure, root-caused:
Also fixed: timeout→ Found while verifying CI on this branch: Dependency Review failed on every PR with "Dependency review is not supported on this repository" — the dependency graph is off for the repo ( Two disclosures:
Upstream reports filed, each with reproduction, workaround and code fix:
Evidence and analysis are in This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $30.317733📊 Context and tokens usage:Claude Opus 5: (6 sub-sessions)
Total: (15.4K new + 606.5K cache writes + 35.4M cache reads) input tokens, 258.8K output tokens, $30.317733 cost 🤖 Models used:
📎 Log file uploaded as Gist (11023KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
🎉 Auto-mergedThis pull request has been automatically merged by hive-mind.
Auto-merged by hive-mind with --auto-merge flag |
Fixes #301.
Problem
JS CI/CD run 33551125228
went red on
mainwithafter all six checks had passed. Auditing that run end to end surfaced a second
class of problem: several steps could not have failed the pipeline even when
they were broken, and the release gates could not tell a timeout from a pass.
Root cause of the reported failure
use-mresolves a package withcreateRequire(...).resolve, so therequirecondition wins and
command-stream@>=0.19.0is loaded through its CommonJSentry point. Node.js 23+ adds a synthetic
'module.exports'named export toCommonJS namespaces, which
use-mdoes not recognise as metadata, so it stopsunwrapping the callable default:
const { $ } = await use('command-stream')therefore yieldsundefined, and thefirst tagged template throws.
scripts/use-module.mjsnormalizes the namespace onevery Node version; the eight release scripts now go through it.
What changed
False negatives — CI could not fail
timeout-minutesis reported by GitHub as cancelled, notfailed, and every release gate reads
needs.<job>.result == 'success', so atimed-out check silently skipped the release and left the run green. Both
pipelines gained a
pipeline-statusjob backed byscripts/check-pipeline-status.sh.bun add "$TARBALL" | tee install.logreportedtee's status (nopipefail);npx prettier --write ".changeset/*.md" || trueandcat ~/.config/opencode/opencode.json || echo "not found"swallowed real failures.npm install -g npm@latestwas unverified, and unpinned: the runner silentlyjumped 11.16.0 → 12.0.2 mid-pipeline. It is now pinned to the tested major and
the OIDC minimum (11.5.1) is asserted.
Cargo.lock), nothing linted the workflows, nothing checked links.produced the silent cancellation above;
scripts/run-with-budget-warning.shnow warns at 70% and kills the process group at 100%.
Errors and warnings
npm warn allow-scriptsfor@parcel/watcherandtree-sitter-bash— nativebuilds were being skipped.
@actions/*packages and thevulnerable
undicithey pulled in are gone;h2bumped past RUSTSEC-2026-0258).LF will be replaced by CRLFon windows-latest, and the AI SDK"System messages in the prompt or messages fields can be a security risk"
warning — both fixed, the latter with a test that pins the warning and its absence.
${{ github.base_ref }}template injection and 11 mutable action tags.New workflows —
workflows.yml(actionlint + shellcheck + zizmor),security.yml(CodeQL, dependency review,npm audit,cargo audit, weekly),links.yml(lychee + Wayback fallback). All seven workflow files now followprinciple 10 of CI-CD-BEST-PRACTICES.md:
per-job
check-…groups that cancel offmain, and a single non-cancellablemain-writer-${{ github.repository }}-maingroup shared by everymainwriter.Tests
New suites:
js/tests/use-module.js,setup-npm.js,check-pipeline-status.js,run-with-budget-warning.js,check-web-archive.js;workflow-policy.jsextendedto accept per-job concurrency and to reject a cancellable writer, so the
concurrency policy cannot regress.
One action left for a repository admin
actions/dependency-review-actionfailed on every pull request with"Dependency review is not supported on this repository" — the dependency graph
is disabled for
link-assistant/agent(GET /dependency-graph/compare/…→ 403,/dependency-graph/sbom→ 404), which no workflow change can turn on. The job nowprobes that API and skips the review with a
::warning::on 403/404 instead offailing the run, and still fails on any other status and on real advisories.
Enabling Settings → Code security → Dependency graph restores the full check
with no further code change;
npm auditandcargo auditcover the lockfilesmeanwhile. That warning annotation is the only one left in the pipeline, together
with the upstream-only
npm warn deprecated node-domexception@1.0.0(fetch-blob#175; even
fetch-blob@4.0.0still depends on it, so it cannot be fixed downstream).Reported upstream
link-foundation/use-m'module.exports'metadata-key fixlink-foundation/js-ai-driven-development-pipeline-templateconst { $ } = await use('command-stream')pattern insetup-npm.mjsand seven sibling scripts, onnode-version: '24.x'runnerslink-foundation/rust-ai-driven-development-pipeline-templatemain-writeconcurrency group does not serializemainwriters from another workflowEvidence and analysis
Every claim above is reproducible from
dev/log/issues/301/pulls/302/:the raw run logs, the timeline, each
requirement of the issue with its
status, the root cause of every
finding, the options considered
including the file-by-file comparison against both pipeline templates, the
components surveyed, and the
findings left open with reasoning.
Debug tracing added by this PR is off by default: set
CI_SCRIPTS_DEBUG=1to havescripts/use-module.mjsprint the resolved module shape, which is what makes aninterop regression like this one readable from a CI log alone.
Note on the changeset
js/.changesetalready carried an unreleased entry andscripts/validate-changeset.mjsallows exactly one changeset per pull request, sothis change is described inside that file rather than added as a second one.