Skip to content

ci(release): fail closed when npm pack reports no tarball; pin the privileged npm upgrade - #57

Open
yakimoto wants to merge 2 commits into
mainfrom
ci/pack-fail-closed
Open

ci(release): fail closed when npm pack reports no tarball; pin the privileged npm upgrade#57
yakimoto wants to merge 2 commits into
mainfrom
ci/pack-fail-closed

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The e2e-smoke gate could pass without ever exercising the tarball it exists to
exercise:

TARBALL="$(npm pack --silent | tail -n1)"
TARBALL="$PWD/$TARBALL"
npm install --no-save "$TARBALL"

Some npm versions suppress the emitted filename on stdout under --silent. When
that happens TARBALL is empty, $PWD/ is a DIRECTORY, and npm install
happily installs the workspace directory instead. Every assertion after it
then passes -- against the working tree, not against the packed artifact. The
gate goes green having proved nothing about what would actually ship.

That is the same fail-open shape as a swallowed error: the check does not
fail, it stops being a check.

Replaced with npm pack --json, whose filename field is not
verbosity-dependent, plus two assertions -- the value ends in .tgz, and the
file exists on disk.

Verified both directions rather than just the happy path:

happy path PACK OK -> wave-av-adk-1.0.2.tgz exit 0
empty TARBALL ::error::npm pack --json reported '', not
a .tgz filename exit 1

A guard that has never been observed rejecting anything is not yet a guard.

Credit where it is due: a peer found this in the mcp-server copy of the same
workflow (bb1bf17) and fixed it there. I had written the --silent version and
copied it across repos, so this propagates their fix to the two that still
carry it.

Also pins the privileged publish job's npm upgrade: npm install -g npm@latest
-> npm@11.19.0. That job holds id-token: write; it should not execute
whatever npm publishes next. adk and mcp-server were pinned by a peer already,
which left this file the only unpinned one of the three.


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.


Note

Low Risk
Changes only affect CI release gates; they tighten validation and pin a known npm CLI without altering application runtime code or publish semantics beyond safer pre-publish checks.

Overview
Release workflow (release.yml) fixes two ways the verify/publish gates could pass without actually validating what ships.

The e2e-smoke step no longer relies on npm pack --silent (empty stdout on some npm versions could make npm install use the workspace directory). It uses npm pack --json, parses the tarball filename, requires a .tgz suffix, and checks the file exists before install/import smoke tests.

The publish job pins the global npm upgrade to npm@11.19.0 instead of @latest on a job with id-token: write, and replaces the inline version check with an env-driven script that fails closed when npm --version is not parseable as x.y.z (the old >/< loop could treat NaN as success).

Reviewed by Cursor Bugbot for commit 14b94f2. Configure here.


Summary by cubic

Harden the release workflow: the e2e-smoke gate installs the real tarball using npm pack --json and fails closed if no .tgz exists; the publish job pins npm@11.19.0 and strictly validates the CLI version before trusted publishing.

  • Bug Fixes

    • Use npm pack --json, parse the filename, assert it ends with .tgz, and confirm the file exists to avoid installing the workspace directory.
    • Validate npm --version via env: require a parseable x.y.z with integers and fail if unparseable or < 11.5.1 so the trusted-publishing check cannot pass silently.
  • Dependencies

    • Pin npm in the trusted-publishing job to npm@11.19.0, avoiding unreviewed upgrades on a job with id-token: write.

Written for commit 14b94f2. Summary will update on new commits.

Review in cubic

Note

Fail closed on missing tarball and pin npm version in release CI

  • Replaces npm pack --silent | tail -n1 with npm pack --json and a Node one-liner to extract the tarball filename reliably; validates the .tgz extension and file existence, emitting ::error:: and exiting 1 on failure.
  • Pins the privileged npm upgrade to npm@11.19.0 instead of latest, and replaces the inline version check with a stricter Node script that parses the version via regex and fails closed if it is unparsable or below 11.5.1.

Macroscope summarized 14b94f2.

The e2e-smoke gate could pass without ever exercising the tarball it exists to
exercise:

  TARBALL="$(npm pack --silent | tail -n1)"
  TARBALL="$PWD/$TARBALL"
  npm install --no-save "$TARBALL"

Some npm versions suppress the emitted filename on stdout under --silent. When
that happens TARBALL is empty, `$PWD/` is a DIRECTORY, and `npm install`
happily installs the workspace directory instead. Every assertion after it
then passes -- against the working tree, not against the packed artifact. The
gate goes green having proved nothing about what would actually ship.

That is the same fail-open shape as a swallowed error: the check does not
fail, it stops being a check.

Replaced with `npm pack --json`, whose filename field is not
verbosity-dependent, plus two assertions -- the value ends in .tgz, and the
file exists on disk.

Verified both directions rather than just the happy path:

  happy path    PACK OK -> wave-av-adk-1.0.2.tgz            exit 0
  empty TARBALL ::error::npm pack --json reported '', not
                a .tgz filename                             exit 1

A guard that has never been observed rejecting anything is not yet a guard.

Credit where it is due: a peer found this in the mcp-server copy of the same
workflow (bb1bf17) and fixed it there. I had written the --silent version and
copied it across repos, so this propagates their fix to the two that still
carry it.

Also pins the privileged publish job's npm upgrade: `npm install -g npm@latest`
-> `npm@11.19.0`. That job holds `id-token: write`; it should not execute
whatever npm publishes next. adk and mcp-server were pinned by a peer already,
which left this file the only unpinned one of the three.
@changeset-bot

changeset-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: facf741

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cursor

cursor Bot commented Aug 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_ded500cc-84b0-4edb-92d8-022ea736f018)

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 7 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b59b06f3-3bac-4c7c-ab6e-16ba7d581964

📥 Commits

Reviewing files that changed from the base of the PR and between 8f51777 and 14b94f2.

📒 Files selected for processing (1)
  • .github/workflows/release.yml

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@yakimoto

yakimoto commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Context for the review: same fail-open fix as wave-av/adk#69npm pack --silent | tail -n1 can yield an empty TARBALL on npm versions that suppress that output, making npm install "$PWD/" install the workspace directory so the smoke gate passes without ever exercising the tarball. Replaced with npm pack --json plus .tgz/exists assertions, and pins npm to 11.19.0 for consistency with the sibling repos. Verified in both directions (happy path exit 0; empty-TARBALL arm exits 1).

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

@yakimoto I will review the release workflow changes. I will check the tarball validation path and the pinned npm version.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@yakimoto

yakimoto commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 39 minutes.

The gate that proves the CLI can do trusted publishing passed for a version
string it could not parse at all.

  const s = 'banana'.split('.').map(Number)   // [NaN]
  s[i] > min[i]   // false
  s[i] < min[i]   // false

Both comparisons are false against NaN, so neither branch was taken, the loop
ran out, and node exited 0. Measured on the shipped expression:

  11.19.0  -> 0     11.5.1 -> 0     11.5.0 -> 1     9.9.9 -> 1
  banana   -> 0     <-- reported "capable" having compared nothing
  11       -> 0     <-- s[1], s[2] undefined -> NaN -> same fall-through

Reachable whenever `npm --version` yields anything but a bare x.y.z: a shim or
proxy printing a banner, npm emitting a second stdout line so NPM_VER is
multi-line, a leading segment carrying a suffix. The step then goes on to
publish on a CLI that may have no trusted-publishing support, which is the one
thing it exists to rule out.

Now the shape is validated before anything is compared, and an unparseable
version is a hard failure rather than a silent pass. The threshold is untouched:
every well-formed version keeps its previous verdict, boundary 11.5.1 included.

  11.19.0 -> 0   11.5.1 -> 0   12.0.0 -> 0   11.19.0-pre.0 -> 0
  11.5.0  -> 1   9.9.9  -> 1   10.9.9 -> 1
  banana  -> 1   ""     -> 1   "11"   -> 1   1e400.0.0 -> 1
  "11.5.1\nextra-line" -> 1

The version also stops being spliced into the program text and is passed
through the environment instead. It is the output of an external command, so
reading it from process.env removes the shell-quoting surface rather than
narrowing it.

actionlint clean (SC2016 suppressed with the reason: the single quotes hold a JS
program, and ${...} inside it are template literals, not shell expansions).
YAML parses.
@cursor

cursor Bot commented Aug 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6378dec6-bcfc-4a8f-8309-d07e7a38793e)

@macroscopeapp

macroscopeapp Bot commented Aug 2, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved 14b94f2

CI workflow hardening that makes release validation fail-closed instead of silently passing. Changes are limited to .github/workflows/release.yml, which the author owns. No runtime code affected.

You can customize Macroscope's approvability policy. Learn more.

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.

1 participant