ci: publish to npm on tag push - #5
Conversation
Releases were blocked on a human with a 2FA recovery code, which is a bad place for a release process to live: the codes are finite, single-use, and spending one is not something you want between a fix and its users. A v* tag now runs the full test matrix, asserts the tag agrees with package.json, asserts the version is not already on the registry, publishes with provenance, and cuts the GitHub Release. The matrix runs again here rather than trusting the run on main, because a tag can point at any commit, including one main never saw. Requires the NPM_TOKEN repository secret: a granular access token scoped to @vib795 with 2FA bypass enabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe workflows add reusable test execution and a tag-triggered release process. The release process validates the tag and package version, prevents duplicate npm publication, publishes with provenance, and creates a GitHub Release. ChangesRelease Pipeline
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubTag
participant TestWorkflow
participant PublishJob
participant NpmRegistry
participant GitHubRelease
GitHubTag->>TestWorkflow: invoke reusable tests
TestWorkflow-->>PublishJob: return test result
PublishJob->>PublishJob: validate tag and package version
PublishJob->>NpmRegistry: publish package with provenance
PublishJob->>GitHubRelease: create release with generated notes
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 65-78: Make the Publish and Cut the GitHub Release steps
independently idempotent: retry npm publication when the package version is
absent, but skip it only after confirming that exact version already exists;
separately check whether the GitHub release for GITHUB_REF_NAME exists and
create it when missing, so reruns can restore only the release without being
blocked by npm view.
- Line 27: Update the actions/checkout@v4 step in the publish job to set
persist-credentials to false, while preserving the explicit GH_TOKEN used by gh
release create.
- Around line 51-60: Update the “Version must not already be published” step to
provide NODE_AUTH_TOKEN to the npm view lookup, distinguish a confirmed
not-found result from authentication, registry, or network errors, and fail the
workflow for every result except confirmed absence; retain the existing error
and success messages for published and unpublished versions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d4443e0f-9b1a-49ab-b76c-3b1fc0d657e4
📒 Files selected for processing (2)
.github/workflows/release.yml.github/workflows/test.yml
| contents: write # cut the GitHub Release | ||
| id-token: write # sign the npm provenance attestation | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"== workflow excerpt =="
sed -n '1,110p' .github/workflows/release.yml
echoecho"== checkout occurrences =="
rg -n "actions/checkout|persist-credentials|permissions:|contents:" .github/workflows/release.ymlRepository: vib795/agent-memory
Length of output: 3213
🌐 Web query:
actions/checkout v4 action.yml persist-credentials default true
💡 Result:
In the actions/checkout v4 action, the persist-credentials input has a default value of true [1][2][3]. When this setting is enabled (the default), the action configures the repository's local git configuration to include the authentication token or SSH key used for the checkout [2][4]. This allows subsequent git commands executed within the runner to be authenticated automatically [2][4]. The action removes this token during the post-job cleanup phase [2][4]. If you wish to prevent the action from adding these credentials to the local git configuration, you must explicitly set persist-credentials to false in your workflow file [2][4][5]: - uses: actions/checkout@v4 with: persist-credentials: false Disabling this setting is often recommended if you are concerned about credential exposure (for example, if you archive and upload the repository as a workflow artifact) [5].
Citations:
- 1: https://github.com/actions/checkout/blob/v4/action.yml
- 2: https://github.com/actions/checkout/blob/v4/README.md
- 3: https://github.com/actions/checkout/blob/master/action.yml
- 4: https://www.github.com/actions/checkout
- 5: https://yossarian.net/til/post/actions-checkout-can-leak-github-credentials/
Disable checkout credential persistence in the publish job.
actions/checkout@v4 leaves persist-credentials enabled by default, so the write-capable checkout token remains available to later commands in this job. Set persist-credentials: false; gh release create already receives a token explicitly through GH_TOKEN.
Suggested change
- uses: actions/checkout@v4
+ with:+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - uses: actions/checkout@v4 | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.29.0)
[warning] 27-27: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml at line 27, Update the actions/checkout@v4
step in the publish job to set persist-credentials to false, while preserving
the explicit GH_TOKEN used by gh release create.
Sources: MCP tools, Linters/SAST tools
| - name: Version must not already be published | ||
| run: | | ||
| set -euo pipefail | ||
| name=$(node -p "require('./package.json').name") | ||
| ver=$(node -p "require('./package.json').version") | ||
| if npm view "$name@$ver" version >/dev/null 2>&1; then | ||
| echo "::error::$name@$ver is already on the registry" | ||
| exit 1 | ||
| fi | ||
| echo "$name@$ver is unpublished; proceeding" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"## workflow excerpt"
nl -ba .github/workflows/release.yml | sed -n '1,120p'echoecho"## node/npm usage in workflow"
grep -nE 'NODE_AUTH_TOKEN|npm|setup-node|package-lock.json|package\.json|registry' .github/workflows/release.yml ||trueechoecho"## package registry/auth hints"
sed -n '1,220p' package.json ||true
[ -f package-lock.json ] && sed -n '1,80p' package-lock.json ||trueRepository: vib795/agent-memory
Length of output: 217
🌐 Web query:
npm view command exit code 404 response npm 404 error exit code documentation
💡 Result:
When the npm CLI encounters an E404 error—most commonly when a requested package or version cannot be found in the registry—it typically exits with a non-zero exit code (specifically 1) [1][2][3]. Key details regarding npm view and E404 behavior include: 1. Exit Code: When npm view fails to find a package or a specific version, it reports an E404 error and terminates with a non-zero exit code (typically 1) [1][2][3]. This ensures that automated scripts and CI/CD pipelines can detect the failure [1][2]. 2. Error Context: The E404 error is explicitly logged to stderr, detailing that the requested resource is not available in the registry [2][3][4]. 3. Notable Edge Cases: There have been documented instances where npm commands may exit with code 0 despite a failure to find specific tags (such as npm view package@latest when the tag itself is missing), which has been a subject of bug reports [1]. 4. General Error Handling: The npm CLI determines exit codes based on error codes (like E404) or defaults to 1 for general failures [5]. If you are encountering this in an automated environment, you should rely on the non-zero exit code to signal that the lookup was unsuccessful, rather than relying solely on the presence or absence of output to stdout [1][2].
Citations:
- 1: [BUG]
npm view package@latestexits with code 0 and no output iflatesttag does not exist npm/cli#6408 - 2: [BUG] npm view --json outputs illegal JSON for existing package but non-existent version npm/cli#3184
- 3: https://stackoverflow.com/questions/70461566/npm-show-outdated-produces-a-e404-is-not-in-this-registry-error
- 4: [BUG]
npm viewdoes not correctly resolve local-only packages npm/cli#5834 - 5: https://deepwiki.com/npm/cli/2.3-error-handling-and-exit-management
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"## release workflow excerpt"
awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,130p'echoecho"## auth/npm related lines"
awk '{if (/NODE_AUTH_TOKEN|npm|setup-node|registry|package-lock|package\.json/) printf "%6d\t%s\n", NR, $0}' .github/workflows/release.yml
echoecho"## package registry/auth hints"
awk '{printf "%6d\t%s\n", NR, $0}' package.json | sed -n '1,220p'
[ -f package-lock.json ] && awk '{printf "%6d\t%s\n", NR, $0}' package-lock.json | sed -n '1,80p'Repository: vib795/agent-memory
Length of output: 6378
🌐 Web query:
GitHub Actions setup-node NODE_AUTH_TOKEN npm view npm publish publishConfig registry-url scoped package
💡 Result:
To publish a scoped package to a specific registry using GitHub Actions and the setup-node action, you must correctly configure both the authentication environment and the registry mapping [1][2]. The setup-node action automates the creation of an.npmrc file that configures your registry and authentication [1][3]. Workflow Configuration 1. Use the setup-node action to define the target registry and the package scope [4][1]. 2. Provide an authentication token via the NODE_AUTH_TOKEN environment variable [1]. Example: - uses: actions/setup-node@v4 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org/' # Or your private registry URL scope: '@your-scope' - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} Key Components - registry-url: This input instructs setup-node to write the registry URL to a project-level.npmrc file [4][3]. - scope: When provided, setup-node configures the.npmrc to associate this scope with the specified registry [3][5]. If not provided, it may default to the repository owner when using the GitHub Packages registry [4][2]. - NODE_AUTH_TOKEN: This environment variable is required by npm to authenticate requests [1]. The setup-node action automatically injects this token into the generated.npmrc [1][3]. Handling publishConfig If you need to ensure a package is always published to a specific registry regardless of local or global.npmrc settings, you can define it in your package.json [6]: { "publishConfig": { "@your-scope:registry": "https://registry.npmjs.org/" } } This configuration is particularly useful for scoped packages to avoid conflicts with default registry settings [7][8]. When using this method, ensure your registry credentials are still properly configured in the environment (e.g., via setup-node) [1]. Important Note: GitHub Packages (GPR) exclusively supports scoped packages (e.g., @owner/package-name) [5][9]. Ensure your package name in package.json adheres to this format if you are targeting GPR [5].
Citations:
- 1: https://docs.github.com/en/actions/tutorials/publish-packages/publish-nodejs-packages
- 2: https://github.com/actions/setup-node/blob/v6/README.md
- 3: https://github.com/actions/setup-node/blob/master/src/authutil.ts
- 4: https://github.com/actions/setup-node
- 5: Unable to publish to Github unless I append username to front actions/setup-node#1108
- 6: https://docs.npmjs.com/cli/v11/configuring-npm/package-json/
- 7: No way to override registry in .npmrc for scoped packages w/--registry flag npm/npm#10117
- 8: Undocumented Feature: publishConfig: { @scope:registry: "" } npm/documentation#677
- 9: Change of registry is being ignored actions/setup-node#763
🌐 Web query:
npm cli npm view private package 404 E404 non-zero exit code npm 404
💡 Result:
When you receive an E404 error with a non-zero exit code while running npm view on a private package, it generally indicates that the npm CLI could not find the package manifest at the specified or default registry [1][2]. This error typically occurs for the following reasons: 1. Lack of Authentication: If the package is private (e.g., restricted access) and you are not authenticated, the registry will return a 404 (Not Found) instead of a 403 (Forbidden) to prevent leaking the existence of private packages to unauthenticated users [3]. Ensure you are logged in (npm login) or have the correct authentication token (e.g., NODE_AUTH_TOKEN) configured for your environment [3]. 2. Incorrect Registry Configuration: npm might be looking at the public registry (registry.npmjs.org) instead of your private registry [4][5]. Check your.npmrc file to ensure your scope is correctly mapped to the intended private registry URL (e.g., @mycompany:registry=https://npm.pkg.github.com/) [5][2]. 3. Workspace/Bulk Operations: If you are running npm view with workspace flags (-ws), the command may fail and exit if it encounters a 404 for any individual workspace, including private ones that have not yet been published or are inaccessible [1][6]. Recent versions of the npm CLI have introduced improvements to handle these workspace-related 404 errors more gracefully [1]. 4. Package or Tag Absence: The package itself, or the specific version/tag you are querying, may genuinely not exist in the targeted registry [7][2]. To debug, you can run the command with increased verbosity to see exactly which registry and URL the CLI is attempting to access: npm view -dddd [5][2] This will help you verify if the CLI is resolving to the registry you expect and if the authentication headers are being included in the request [5][2].
Citations:
- 1: fix(view): dont immediately exit on first workspace 404 npm/cli#7508
- 2: [BUG] npm view --registry option not respected npm/cli#7904
- 3:
nx release publishskipsnpm viewpre-check for restricted/private scoped packages, causing 403 on unchanged versions nrwl/nx#35235 - 4:
npm viewdoes not handle registry correctly npm/npm#11634 - 5: [BUG] npm view does not respect registry for @scope in .npmrc npm/cli#8103
- 6: [BUG] npm view --json command returns not only JSON if failed npm/cli#5444
- 7: [BUG]
npm view package@latestexits with code 0 and no output iflatesttag does not exist npm/cli#6408
🌐 Web query:
npm view npm view package version output empty stderr exit code 0
💡 Result:
When you run the npm view command and it returns an empty output with an exit code of 0, it typically indicates that the command successfully executed but could not find the specific package, version, or tag you requested [1][2][3]. Historical context and behavior: - Legacy Behavior: In older versions of the npm CLI, npm view would frequently return an empty string and a success exit code (0) when a requested version or tag did not exist in the registry [4][5][6]. This was often used by automated scripts to verify if a package was missing [4][5]. - Recent Changes: Newer versions of the npm CLI have been updated to treat these cases as errors, generally returning an E404 error code and a non-zero exit code [4][7][6]. If you are encountering an empty output with a zero exit code, you are likely using an older version of npm or interacting with a specific registry configuration that causes this behavior [2][3][6]. - Underlying Cause: The primary cause is often that the package does not have a version tagged as "latest" (which is the default tag if none is specified), or the specific version/tag requested does not exist on the target registry [1][2][4]. Recommendations: - Check your npm version: Run npm -v to see if you are using an older version. Upgrading may provide more consistent error reporting [6]. - Verify the registry: If you are using a private registry (like Nexus or GitHub Packages), ensure it is configured correctly, as some third-party registries may not handle metadata requests in the exact way the npm CLI expects [1][2][3]. - Handle empty output in scripts: If you must rely on existing behavior, ensure your scripts are designed to handle both empty output and potential error states, especially as newer versions of npm are moving toward stricter E404 reporting [4][5].
Citations:
- 1: [BUG]
npm view package@latestexits with code 0 and no output iflatesttag does not exist npm/cli#6408 - 2: [BUG] npm view/show result is empty if "latest" tag does not exist npm/cli#4029
- 3: https://stackoverflow.com/questions/55122545/npm-view-not-returning-data-from-nexus
- 4: fix(view): error on missing version npm/cli#5035
- 5: [BUG] npm info exits with zero exit code if package@version is not found npm/cli#4964
- 6: [BUG] npm view --json outputs illegal JSON for existing package but non-existent version npm/cli#3184
- 7: fix(view): error on missing version npm/cli#5035
Fail closed when checking npm publication state.
The current check treats every failed npm view result as “unpublished”, including auth failures, registry errors, and network failures. NODE_AUTH_TOKEN is only provided during npm publish, so npm view runs as an anonymous public lookup and cannot distinguish an unpublished version from a private package or transient failure. Provide the token on this step and abort unless the lookup is confirmed absent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml around lines 51 - 60, Update the “Version must
not already be published” step to provide NODE_AUTH_TOKEN to the npm view
lookup, distinguish a confirmed not-found result from authentication, registry,
or network errors, and fail the workflow for every result except confirmed
absence; retain the existing error and success messages for published and
unpublished versions.
Source: MCP tools
| - name: Publish | ||
| run: npm publish --provenance --access public | ||
| env: | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
| - name: Cut the GitHub Release | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh release create "$GITHUB_REF_NAME" \ | ||
| --title "$GITHUB_REF_NAME" \ | ||
| --generate-notes \ | ||
| --verify-tag |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"== workflow excerpt =="if [ -f .github/workflows/release.yml ];then
sed -n '1,120p' .github/workflows/release.yml | cat -n
elseecho"missing .github/workflows/release.yml"fiecho"== release workflow search =="
rg -n "npm publish|gh release create|verify-tag|package.json|RELEASE|GITHUB_REF_NAME|GH_TOKEN|NPM_TOKEN|version" .github/workflows/release.yml .github/workflows 2>/dev/null ||trueecho"== package manifests =="forfin package.json pnpm-lock.yaml package-lock.json yarn.lock;doif [ -f"$f" ];thenecho"--- $f ---"
rg -n '"version"|"name"|engines|private|publishConfig|workspaces|npm'"$f" -m 50 ||truefidoneRepository: vib795/agent-memory
Length of output: 7244
Make npm publication and GitHub Release creation recoverable.
The workflow writes npm and GitHub independently. If npm publish succeeds but gh release create fails, rerunning the workflow skips the Release step after npm view "$name@$ver" succeeds. Add an idempotent recovery path that lets a rerun publish-only failures be retried and skip a confirmed existing package version when restoring only the missing GitHub Release.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml around lines 65 - 78, Make the Publish and Cut
the GitHub Release steps independently idempotent: retry npm publication when
the package version is absent, but skip it only after confirming that exact
version already exists; separately check whether the GitHub release for
GITHUB_REF_NAME exists and create it when missing, so reruns can restore only
the release without being blocked by npm view.
No functional change. This exercises the tag-triggered publish path added in #5 end to end: matrix gate, version guards, provenance signing, GitHub Release. A release pipeline that has never run is not a release pipeline, and a patch with nothing in it is the cheapest way to find that out. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Publishing
0.1.0cost a 2FA recovery code. The codes are finite and single-use, which is a poor place for a release process to live.What a
v*tag now doestest.ymlgained aworkflow_calltrigger so this reuses it rather than duplicating it)package.jsonversion — a mismatch means someone tagged the wrong commit or skipped the bump, and npm never gives a version number back--provenance, which ties the tarball to the commit and workflow run and shows the badge on npm--verify-tagThe matrix runs here rather than trusting the run on
main, because a tag can point at any commit — including onemainnever saw.Before this can work
Add an
NPM_TOKENrepository secret: a granular access token scoped to@vib795, read+write, with bypass 2FA enabled. Without it the publish step fails on auth.Not retroactive
v0.1.0was tagged and published by hand before this existed, so this takes effect from the next tag.🤖 Generated with Claude Code
Summary by CodeRabbit