Skip to content

fix: retry and bound the npm audit call behind check:audit - #1763

Merged
mrbobbytables merged 2 commits into
mainfrom
quality/audit-gate-registry-retry
Oct 22, 2026
Merged

mrbobbytables merged 2 commits into
mainfrom
quality/audit-gate-registry-retry

Conversation

@hivecommons-hive

@hivecommons-hive hivecommons-hive Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Retry npm audit failures that produce no usable report, while enforcing an
actual subprocess deadline rather than treating a per-request timeout as a
whole-process guarantee.

  • Retain three attempts and two-second delays between attempts.
  • Keep --fetch-timeout=60000 and additionally enforce a 60,000-ms
    spawnSync timeout with SIGKILL, so npm's retries, fallback requests or
    signal handling cannot indefinitely extend the audit subprocess.
  • Budget approximately 184 seconds for the three subprocess deadlines and
    two delays, plus process launch and reporting overhead. This is not a
    guarantee for the duration of the entire CI job.
  • Surface an explicit deadline diagnostic on timeout. A timed-out process's
    partial JSON is not accepted as a completed audit.
  • Preserve fail-closed exhaustion, distinguishable spawn/parse/report
    diagnostics, advisory classification and the documented allowlist.
  • A usable report remains final, including reports identifying a blocked
    advisory; retries cannot discard an actual finding.

Regression evidence

The existing CLI sandbox now records the actual subprocess timeout and kill
signal and can synthesize deadline errors without adding minute-long waits
to the suite. Focused cases cover the exact 60,000-ms deadline on every
attempt, recovery after a timed-out partial report, exhausted deadline
failures and the existing one-attempt finality for real advisory reports.

Local validation: 49 focused cases; the full required unit coverage gate
passes with 3,034 tests, source 100% lines/regions and harness 100% lines /
99.77% regions. All six lint gates, the live dependency audit and the
production build pass. The live audit's existing allowlisted findings are
unchanged; no dependency or allowlist update is included.

Maintainer authorization

The maintainer explicitly approved this bounded repair to the existing PR
and delegated hold release and exact-head protected queue submission after
required checks pass under the documented sole-maintainer path. An agent
review is not independent human approval; no protection is bypassed.

Closes #1762.

scripts/audit-gate.mjs made exactly one npm audit call and failed the
gate when that call came back without a report, with no retry and no
bound on how long the call could take. One bad answer from the registry
therefore reddened the required Validate repository check on every open
pull request at once, with nothing in any of those diffs to fix.

The npm bundled with Node 22 posts to the bulk advisory endpoint first
and, on any failure, falls back to the retired quick-audit endpoint,
which now answers 400. The 400 is what reaches the log while the bulk
failure that caused the fallback stays hidden, so a transient fault
reads as a lockfile problem.

npm defaults fetch-timeout to 300000, and one hung bulk request really
did spend all of it. The job running this gate is capped at fifteen
minutes, so three attempts at that default would have replaced a bad
diagnostic with no diagnostic at all; each attempt is now capped at
sixty seconds, which keeps the whole loop cheaper than one attempt is
today.

Only the answers carrying no report are retried. A parsed report stays
final whatever it says, so a genuinely new advisory is never retried
away, and every exhausted path still exits 1 — the gate fails closed
exactly as before.

Closes #1762

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

Important

Held for human review by the hive's ACMM level gate.

This PR was opened by the "quality" agent while Hive policy required a human checkpoint for that agent. Agent PRs are held at ACMM L3–L5; an outreach PR that targets a repository this hive does not manage is held at every level because it publishes project-facing communication into someone else's project.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

@netlify

netlify Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

Name Link
🔨 Latest commit 51da80e
🔍 Latest deploy log https://app.netlify.com/projects/endusers/deploys/6ada0fd5cc854f4b56f1d443
😎 Deploy Preview https://deploy-preview-1763--endusers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@hivecommons-hive

Copy link
Copy Markdown
Contributor Author
011a0b7

ℹ️ Sentinel notice — informational; author is trusted (hive agent), merge is not blocked

Hive flagged this PR (author @hivecommons-hive[bot], head 011a0b71dff3) because it matches behaviors that can override security controls, escalate privileges or damage the codebase. This is a heuristic, not an accusation — a maintainer should confirm the change is intended before it merges.

  • ci_gate_weakening — CI configuration is deleted, softened or has gate checks removed (Disables or softens CI gates (continue-on-error, || true, removed checks))
    • scripts/audit-gate.mjs

Hive did not add the sentinel-alert label because this author is trusted. The finding remains recorded for audit and the dashboard Security tab; set sentinel.trusted_authors_block: true to require the blocking label for trusted authors too.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the diff against the tree at the reviewed head (011a0b7), ran node --test tests/audit-gate.test.mjs (47 tests, 0 fail), and verified the surrounding claims. This looks correct to me.

What I checked:

  • Fail-closed invariant holds. runAudit() (scripts/audit-gate.mjs) returns failure: null only when report.vulnerabilities is a non-null object; main() retries only while last.failure !== null, so a parsed report — including one naming a new advisory — is final on the first attempt. The added test "a report naming a new advisory is never retried away" asserts attempts === 1 with exit 1, which pins this.
  • Every exhausted path still exits 1. The three failure arms (spawn, unparseable, no-report) each reach process.exit(1) in main(), and the three diagnostics stay distinguishable, matching the pre-existing test assertions.
  • Retry bound is real, not just claimed. The loop runs at most AUDIT_ATTEMPTS = 3; the "retrying stops at the attempt limit" test asserts the stub was invoked exactly 3 times, so an unbounded loop can't regress silently.
  • --fetch-timeout=60000 is on every attempt (runAudit's argv), asserted per-invocation via the stub's argv log — the flag can't be dropped without a test failure.
  • Scope claims check out: check:audit is node scripts/audit-gate.mjs (package.json:26); stryker.config.mjs:62 mutates src/** and scripts/lib/** only, so the mutation lane is indeed untouched; the diff touches only the two files claimed, disjoint from #1761's tests/validate-utils.test.mjs.

One nit, not a defect: the body's "Only answers carrying no report are retried" is slightly broader in the code — spawn and parse failures are retried too (all three arms return report: null). The last added test states this explicitly ("a missing npm is retried too"), so the behavior is intentional and tested; just noting the prose is looser than the loop.

PR is hold-gated; a maintainer still needs to lift the hold. The change is low-risk (CI-only script, no workflow files touched) and all checks are green on this head.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88

Bound each audit process independently of npm request retries, preserve fail-closed handling, and cover deadline recovery and exhaustion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: mrbobbytables <mrbobbytables@users.noreply.github.com>
@mrbobbytables

Copy link
Copy Markdown
Member

Maintainer-delegated sole-maintainer disposition: the maintainer explicitly approved this existing-PR repair and delegated hold release and normal protected queue submission after the repaired head passed checks. Reviewed exact head 51da80e: the process deadline, retry/finality behavior and timeout diagnostics match the approved scope; no allowlist or protection changes. Required Validate repository and Lint repository, CodeQL, DCO, browser tests and browser coverage are green on this head. Agent inspection is not independent human approval. Releasing only this PR hold and submitting this exact head to the protected queue; no bypass.

@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit c501a33 Oct 22, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] scripts/audit-gate.mjs gives the npm registry one unbounded attempt, so a single registry fault reds a required check on every open PR

1 participant