Line references corrected 2026-09-07 at adab560. The three line numbers
in this issue were wrong when it was filed, and the grep -n output pasted
under "Getting started" reproduces on no commit this repository has. My error,
not a change under you. The step is at 197-198, the surrounding numbers are
in the corrected block below, and the substance of the issue is unchanged:
frontend is still required, the step is still enforced, and it still gates
every pull request on registry.npmjs.org.
frontend is a required status check on main, and its fourth step is:
- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high
That command POSTs to https://registry.npmjs.org/-/npm/v1/security/advisories/bulk.
When the endpoint is unavailable, npm exits 1, and the check fails exactly the way
it fails for a genuine high-severity advisory.
This morning that took every open pull request down for 45 minutes:
| run |
PR |
started (UTC) |
error |
| 33859810636 |
#365 |
09:44 |
npm warn audit 503 Service Unavailable - POST .../advisories/bulk |
| 33862001249 |
#366 |
10:11 |
npm warn audit network timeout at: .../advisories/bulk |
| 33863121379 |
#366 |
10:25 |
503 Service Unavailable |
Each attempt ran for five minutes before npm gave up (10:25:37 → 10:30:37 on the
last one), so a bad morning at the registry costs runner minutes on top of the
blocked merges. Re-running the failed job cleared all three.
Two things make this worse than an ordinary flake.
The audit covers apps/sysknife-shell, the paused Tauri GUI. CONTRIBUTING.md
puts GUI work out of scope, so #366, which changes one line of CONTRIBUTING.md,
waited twice on that directory's dependency advisories.
And gh pr checks prints frontend fail either way. A contributor reading that
cannot separate an outage from a vulnerability without opening the log.
Three shapes, and I hold none of them strongly:
- Move the audit into its own scheduled workflow against
main, daily, and drop
it from the pull-request path. Advisories appear on the registry's clock. A
PR-time audit answers a question the pull request did not ask.
- Keep it on pull requests and stop it blocking:
continue-on-error: true, so
the result is reported and not enforced.
- Leave the PR-shaped half to
dependency-review, which already runs here and
already catches a dependency a pull request introduces.
Whoever picks this up: the step is .github/workflows/ci.yml:197-198. Branch
protection on main lists frontend among its required contexts, so deleting
the step is enough; deleting the job would need the protection setting changed
in the same breath or every pull request sits on a check that never reports.
Getting started
One workflow file. No VM, no daemon, no LLM provider, no credentials.
$ grep -n 'npm audit' .github/workflows/ci.yml
198: run: npm audit --omit=dev --audit-level=high
$ awk 'NR>=170 && NR<=230 && /- name:/ {print NR": "$0}' .github/workflows/ci.yml
177: - name: Lint GitHub issue templates
186: - name: Checkout
189: - name: Set up Node
194: - name: Install dependencies
197: - name: Audit production dependencies
200: - name: Type check
203: - name: Test
216: - name: Checkout
219: - name: Install Tauri system dependencies
229: - name: Set up Rust
That window is wider than the frontend job, so it picks up the tail of
docs-and-hygiene at 177 and the head of the next job at 216. Steps 186 through
203 are the ones this issue is about.
The job runs with working-directory: apps/sysknife-shell, which is the paused
desktop app, so this step gates every pull request in the repository on the
availability of registry.npmjs.org for a component nobody is developing.
Confirm the branch-protection half before you touch anything, because it decides
which of the three options in the body is safe:
$ gh api repos/lacs-project/sysknife/branches/main/protection --jq '.required_status_checks.contexts'
frontend is in that list. Deleting the step leaves the job reporting. Deleting
the job leaves every pull request waiting on a context that never arrives.
Tests first
The awkward part is that the thing you are fixing is a network outage, and you
cannot make registry.npmjs.org fail on demand.
Break what the guard protects. Whichever option you take, assert on the
workflow text rather than on a run. A guard under tests/release/ that parses
the frontend job and asserts the audit step carries continue-on-error: true,
or that no npm audit invocation is enforced on the pull-request path, is
checkable offline and cannot pass by accident.
Break the guard's own input. This is the failure mode #346, #362 and #368 all
share, so it is worth care here. Assert on a count, not only on a match. If your
guard greps the job for a token, it passes just as happily when the job is renamed
out from under it and it reads zero steps. Give it a fixture whose frontend job
has the step enforced, and assert it goes red on that fixture. A guard that has
never been seen to fail is not a guard.
tests/release/postgres-contract-guard.test.sh is the closest existing model for
parsing a job out of ci.yml, and #362 is the open issue about the ways that
particular pattern can fool you.
Difficulty
easy. The change is a few lines of YAML. Choosing among the three options is the
judgement, and the body already lays them out.
frontendis a required status check onmain, and its fourth step is:That command POSTs to
https://registry.npmjs.org/-/npm/v1/security/advisories/bulk.When the endpoint is unavailable, npm exits 1, and the check fails exactly the way
it fails for a genuine high-severity advisory.
This morning that took every open pull request down for 45 minutes:
npm warn audit 503 Service Unavailable - POST .../advisories/bulknpm warn audit network timeout at: .../advisories/bulk503 Service UnavailableEach attempt ran for five minutes before npm gave up (10:25:37 → 10:30:37 on the
last one), so a bad morning at the registry costs runner minutes on top of the
blocked merges. Re-running the failed job cleared all three.
Two things make this worse than an ordinary flake.
The audit covers
apps/sysknife-shell, the paused Tauri GUI.CONTRIBUTING.mdputs GUI work out of scope, so #366, which changes one line of
CONTRIBUTING.md,waited twice on that directory's dependency advisories.
And
gh pr checksprintsfrontend faileither way. A contributor reading thatcannot separate an outage from a vulnerability without opening the log.
Three shapes, and I hold none of them strongly:
main, daily, and dropit from the pull-request path. Advisories appear on the registry's clock. A
PR-time audit answers a question the pull request did not ask.
continue-on-error: true, sothe result is reported and not enforced.
dependency-review, which already runs here andalready catches a dependency a pull request introduces.
Whoever picks this up: the step is
.github/workflows/ci.yml:197-198. Branchprotection on
mainlistsfrontendamong its required contexts, so deletingthe step is enough; deleting the job would need the protection setting changed
in the same breath or every pull request sits on a check that never reports.
Getting started
One workflow file. No VM, no daemon, no LLM provider, no credentials.
That window is wider than the
frontendjob, so it picks up the tail ofdocs-and-hygieneat 177 and the head of the next job at 216. Steps 186 through203 are the ones this issue is about.
The job runs with
working-directory: apps/sysknife-shell, which is the pauseddesktop app, so this step gates every pull request in the repository on the
availability of
registry.npmjs.orgfor a component nobody is developing.Confirm the branch-protection half before you touch anything, because it decides
which of the three options in the body is safe:
frontendis in that list. Deleting the step leaves the job reporting. Deletingthe job leaves every pull request waiting on a context that never arrives.
Tests first
The awkward part is that the thing you are fixing is a network outage, and you
cannot make
registry.npmjs.orgfail on demand.Break what the guard protects. Whichever option you take, assert on the
workflow text rather than on a run. A guard under
tests/release/that parsesthe
frontendjob and asserts the audit step carriescontinue-on-error: true,or that no
npm auditinvocation is enforced on the pull-request path, ischeckable offline and cannot pass by accident.
Break the guard's own input. This is the failure mode #346, #362 and #368 all
share, so it is worth care here. Assert on a count, not only on a match. If your
guard greps the job for a token, it passes just as happily when the job is renamed
out from under it and it reads zero steps. Give it a fixture whose
frontendjobhas the step enforced, and assert it goes red on that fixture. A guard that has
never been seen to fail is not a guard.
tests/release/postgres-contract-guard.test.shis the closest existing model forparsing a job out of
ci.yml, and #362 is the open issue about the ways thatparticular pattern can fool you.
Difficulty
easy. The change is a few lines of YAML. Choosing among the three options is thejudgement, and the body already lays them out.