Skip to content

fix: fail CLI releases early on npm token mismatch - #1387

Merged
Nikhil (shadowfax92) merged 3 commits into
mainfrom
fix/cli-npm-publish
Jun 25, 2026
Merged

Nikhil (shadowfax92) merged 3 commits into
mainfrom
fix/cli-npm-publish

Conversation

@shadowfax92

Copy link
Copy Markdown
Contributor

Summary

  • Add a tested npm publish-access preflight for browseros-cli that checks the token user and read-write collaborator access.
  • Run Node setup, make npm-version, and the npm preflight before CDN upload or GitHub release creation.
  • Normalize the npm package repository URL and document the NPM_TOKEN ownership requirement.

Design

The release still publishes npm after GitHub release assets exist, so npm postinstall can fetch the matching cli/vX.Y.Z binaries. The new preflight catches bad npm credentials before those public release side effects.

Test plan

  • bun test scripts/build/cli/npm-access.test.ts scripts/build/cli/release-policy.test.ts
  • actionlint ../../.github/workflows/release-cli.yml
  • bun run check (exit 0; existing Claw warnings still printed)
  • cd apps/cli && gofmt -l . && go vet ./... && go build ./... && go test ./...
  • temp browseros-cli@0.3.1 npm publish --dry-run --access public

Note

The existing failed cli/v0.3.1 tag run still needs a valid npm token with browseros-cli read-write access; this PR prevents future runs from discovering that only after partial release side effects.

@shadowfax92

Copy link
Copy Markdown
Contributor Author

Claude (@claude) review

@shadowfax92

Copy link
Copy Markdown
Contributor Author

Greptile (@greptileai) review

@github-actions github-actions Bot added the fix label Jun 25, 2026
@claude

claude Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

Claude finished Nikhil (@shadowfax92)'s task in 2m 6s —— View job


Code Review

  • Gather context
  • Read changed files
  • Analyze for issues
  • Post review

Overall this is a clean, well-structured preflight. The injection of NpmRunner makes the logic fully testable without hitting the real npm CLI. The four test cases cover the key failure paths. A few things worth looking at before merge:


Medium-priority

npm access list collaborators compatibility with npm v10 (npm-access.ts:73–79)

Node 20 ships with npm v10, which restructured the npm access subcommand surface. npm access list collaborators <pkg> <user> accepting a positional <user> filter is undocumented and was already inconsistently supported across npm versions. In npm v10 the output shape for npm access list collaborators was also changed. If the command silently returns {} when the user filter matches nothing (rather than the expected {"<user>": "read-write"}), parseCollaboratorAccess returns '' and the error fires — which is the safe failure mode — but if the command itself fails with a non-zero exit on npm v10, the error message will be the raw npm error rather than the helpful message about access.

Consider testing npm access list collaborators browseros-cli --json manually against npm v10 before relying on it in production. Alternatively, fetch the full collaborators object (npm access list collaborators browseros-cli --json without the user positional) and filter in TypeScript — this is less ambiguous and works across npm versions. Fix this →


Low-priority

Missing test for auth failure (npm-access.test.ts)

The four tests cover non-owner, empty owner list, and read-only access, but there's no test for the case where npm whoami throws (bad token, network error). The runWhoami catch block (line 55–59) has a distinct, user-friendly error message — worth a test to make sure that path is exercised.

Silently defaulting to browseros-cli (npm-access.ts:121)

parseArgs defaults to 'browseros-cli' when no --package flag is passed. For a CI script this is fine, but it means a bare invocation (bun scripts/build/cli/npm-access.ts) silently runs against the real package. A minor risk if someone invokes this in a context without the flag.


Nitpicks

parseCollaboratorAccess case-insensitive fallback (npm-access.ts:109)

The collaborators[user.toLowerCase()] fallback is defensive but creates a subtle inconsistency: owners.includes(user) (line 31) is case-sensitive using the output of npm owner ls, while the collaborators lookup accepts a different-cased entry. npm usernames are case-insensitive in practice so this is unlikely to matter, but the fallback could theoretically let through a user that failed the owners check if names are cased differently between the two npm endpoints.

execFileSync stderr piped but not surfaced on success (npm-access.ts:114–117)

stdio: ['ignore', 'pipe', 'pipe'] is correct — stderr is captured so it shows up on error via error.stderr in errorMessage(). Just noting that npm sometimes emits deprecation warnings to stderr on success; those are silently swallowed. That's intentional and fine, just worth being aware of if npm v10 emits relevant output there.


Things that look good

  • Workflow ordering is correct: preflight runs after setup-node/make npm-version but before CDN upload and GitHub release, which is exactly the stated goal.
  • bun install not required for the preflight step: npm-access.ts only imports node:child_process so it runs without needing the monorepo deps installed. The bun install step later in the workflow doesn't block this.
  • NODE_AUTH_TOKEN correctly set: setup-node with registry-url writes .npmrc with _authToken=${NODE_AUTH_TOKEN}, and the preflight step sets the env var — npm will pick it up automatically.
  • package.json repository URL normalization (npm/package.json): the git+... prefix and .git suffix are the canonical npm format.
  • release-policy.test.ts referenced in test plan but not in this diff — that pre-existing test passing is good signal.
  • Error messages are actionable: each throw includes the package name, the authenticated user, and the expected owners — exactly what a release engineer needs to debug a bad token.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Tests passed — 1423/1427

Suite Passed Failed Skipped
✅ agent 272/272 0 0
✅ build 41/41 0 0
⚠️ claw-app 0/0 0 0
⚠️ claw-server 0/0 0 0
✅ eval 91/91 0 0
✅ server-agent 301/301 0 0
✅ server-api 143/143 0 0
✅ server-browser 10/10 0 0
✅ server-integration 10/10 0 0
✅ server-lib 256/257 0 1
✅ server-root 47/50 0 3
✅ server-tools 252/252 0 0

View workflow run

@shadowfax92
Nikhil (shadowfax92) merged commit eaa0a0a into main Jun 25, 2026
22 checks passed
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an early npm access check for CLI releases. The main changes are:

  • Node setup and npm package versioning moved before release side effects.
  • A Bun preflight checks the npm token user, package owners, and collaborator access.
  • Tests cover the new npm access helper.
  • CLI release docs and npm package repository metadata were updated.

Confidence Score: 4/5

The npm preflight can block valid CLI releases until its real npm output handling is tightened.

  • The workflow order and package version preparation look consistent with the existing release flow.
  • The access parser assumes one collaborator JSON shape from the npm CLI.
  • The error helper can hide useful npm response details during release failures.

packages/browseros-agent/scripts/build/cli/npm-access.ts

Important Files Changed

Filename Overview
.github/workflows/release-cli.yml Adds early Node setup, npm package preparation, and npm access verification before CDN upload and GitHub release creation.
packages/browseros-agent/scripts/build/cli/npm-access.ts Adds the npm publish-access preflight, with issues around collaborator JSON parsing and failed-command diagnostics.
packages/browseros-agent/scripts/build/cli/npm-access.test.ts Adds mocked unit tests for owner validation, missing owner data, and read-only access rejection.
packages/browseros-agent/apps/cli/npm/package.json Normalizes the npm package repository URL.
packages/browseros-agent/apps/cli/README.md Documents the npm token ownership requirement for CLI releases.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/browseros-agent/scripts/build/cli/npm-access.ts:108-110
**Collaborator Access Shape Mismatch**

The release workflow now depends on this parser accepting the real `npm access list collaborators ... --json` output from the Node 20 npm CLI. If npm returns collaborator records as an array, or omits package owners from the collaborator ACL after `owner ls` already proved ownership, this lookup returns an empty access string and rejects a valid publish token before the release can continue.

### Issue 2 of 2
packages/browseros-agent/scripts/build/cli/npm-access.ts:141-142
**Stdout Diagnostics Are Dropped**

When an npm command exits non-zero and writes its structured error to stdout, this helper ignores that output and falls back to the generic process error message. A failed preflight can then hide the registry response that explains why the release was blocked.

```suggestion
    const stderr = 'stderr' in error ? String(error.stderr).trim() : ''
    const stdout = 'stdout' in error ? String(error.stdout).trim() : ''
    return stderr || stdout || error.message
```

Reviews (1): Last reviewed commit: "fix: verify cli npm collaborator access" | Re-trigger Greptile

Comment on lines +108 to +110
const collaborators = parsed as Record<string, unknown>
const access = collaborators[user] ?? collaborators[user.toLowerCase()]
return typeof access === 'string' ? access : ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Collaborator Access Shape Mismatch

The release workflow now depends on this parser accepting the real npm access list collaborators ... --json output from the Node 20 npm CLI. If npm returns collaborator records as an array, or omits package owners from the collaborator ACL after owner ls already proved ownership, this lookup returns an empty access string and rejects a valid publish token before the release can continue.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browseros-agent/scripts/build/cli/npm-access.ts
Line: 108-110

Comment:
**Collaborator Access Shape Mismatch**

The release workflow now depends on this parser accepting the real `npm access list collaborators ... --json` output from the Node 20 npm CLI. If npm returns collaborator records as an array, or omits package owners from the collaborator ACL after `owner ls` already proved ownership, this lookup returns an empty access string and rejects a valid publish token before the release can continue.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +141 to +142
const stderr = 'stderr' in error ? String(error.stderr).trim() : ''
return stderr || error.message

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Stdout Diagnostics Are Dropped

When an npm command exits non-zero and writes its structured error to stdout, this helper ignores that output and falls back to the generic process error message. A failed preflight can then hide the registry response that explains why the release was blocked.

Suggested change
const stderr = 'stderr' in error ? String(error.stderr).trim() : ''
return stderr || error.message
const stderr = 'stderr' in error ? String(error.stderr).trim() : ''
const stdout = 'stdout' in error ? String(error.stdout).trim() : ''
return stderr || stdout || error.message
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browseros-agent/scripts/build/cli/npm-access.ts
Line: 141-142

Comment:
**Stdout Diagnostics Are Dropped**

When an npm command exits non-zero and writes its structured error to stdout, this helper ignores that output and falls back to the generic process error message. A failed preflight can then hide the registry response that explains why the release was blocked.

```suggestion
    const stderr = 'stderr' in error ? String(error.stderr).trim() : ''
    const stdout = 'stdout' in error ? String(error.stdout).trim() : ''
    return stderr || stdout || error.message
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves npm credential checks earlier in the CLI release flow. The main changes are:

  • Early Node setup and npm package version preparation in the release workflow.
  • A new Bun preflight that checks npm owner and collaborator access.
  • Tests for the new npm access helper.
  • Normalized npm package repository metadata.
  • README guidance for the release NPM_TOKEN.

Confidence Score: 4/5

The changed npm preflight can block valid CLI releases.

  • The workflow ordering and working directories line up with the existing CLI package layout.
  • The collaborator access check can read the wrong JSON shape when it passes the username filter to npm.
  • The README does not fully match the new access requirement.

packages/browseros-agent/scripts/build/cli/npm-access.ts; packages/browseros-agent/apps/cli/README.md

Important Files Changed

Filename Overview
.github/workflows/release-cli.yml Moves npm setup, package versioning, and access verification before public release side effects.
packages/browseros-agent/scripts/build/cli/npm-access.ts Adds the npm access preflight, with a collaborator lookup that can reject valid owner tokens.
packages/browseros-agent/scripts/build/cli/npm-access.test.ts Adds tests for owner, empty-owner, and read-only access cases.
packages/browseros-agent/apps/cli/npm/package.json Updates the repository URL to npm’s git URL form.
packages/browseros-agent/apps/cli/README.md Documents the npm token ownership requirement, but omits the read-write access requirement.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/browseros-agent/scripts/build/cli/npm-access.ts:78
**Username Filter Hides Access**

When the release workflow runs this preflight with a valid owner token, the optional `<user>` argument can make `npm access list collaborators` return entries keyed by teams or omit the individual owner entry. `parseCollaboratorAccess` then looks only for `collaborators[user]`, treats the missing key as no access, and aborts the release before any assets are uploaded.

### Issue 2 of 2
packages/browseros-agent/apps/cli/README.md:94
**Read-Write Requirement Missing**

This release note says the secret only needs to authenticate as an npm owner, but the new preflight also rejects tokens without `read-write` collaborator access. A release engineer can follow the README, rotate the secret to an owner account that fails the collaborator-access check, and hit an unexpected release failure.

```suggestion
The `NPM_TOKEN` release secret must authenticate as an npm owner of `browseros-cli` with read-write package access; the workflow checks this before uploading CDN assets or creating the GitHub release.
```

Reviews (2): Last reviewed commit: "fix: verify cli npm collaborator access" | Re-trigger Greptile

'list',
'collaborators',
packageName,
user,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Username Filter Hides Access

When the release workflow runs this preflight with a valid owner token, the optional <user> argument can make npm access list collaborators return entries keyed by teams or omit the individual owner entry. parseCollaboratorAccess then looks only for collaborators[user], treats the missing key as no access, and aborts the release before any assets are uploaded.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browseros-agent/scripts/build/cli/npm-access.ts
Line: 78

Comment:
**Username Filter Hides Access**

When the release workflow runs this preflight with a valid owner token, the optional `<user>` argument can make `npm access list collaborators` return entries keyed by teams or omit the individual owner entry. `parseCollaboratorAccess` then looks only for `collaborators[user]`, treats the missing key as no access, and aborts the release before any assets are uploaded.

How can I resolve this? If you propose a fix, please make it concise.

Vasilev Dmitrii (gHashTag) added a commit to gHashTag/BrowserOS that referenced this pull request Sep 4, 2026
…, and close-done kept its own copy of the rule (#360)

`tri why` warned ahead: all nine remaining accepted branches conflict, so nothing
can land, close-done will close nothing, and the pipeline stops as soon as those
boundaries are all that is left. Four of the nine were finished work.

THE ROUTE NO COMPARISON OF BYTES CAN TAKE. `isLanded` had four routes -
ancestry, an identical merged tree, a patch-id match, and a hand-applied change.
Every one of them compares CONTENT. They are all blind to the thing this loop
does constantly: when a bee's branch goes stale, I re-cut its change against the
current base and squash-merge THAT. The carry is a new commit with a new tree and
a new patch-id, so nothing content-shaped connects it back, and the bee's
original branch becomes permanent debt - re-offered every round, conflicting
every round, holding its boundary fenced for ever.

So read the message. L1 of this repository is "no code merged without
`Closes #N`", which makes the message a load-bearing record and not a courtesy:

  browseros-ai#1310  landed as PR #330
  browseros-ai#1308  landed as PR #331
  browseros-ai#1362  `Closes browseros-ai#1362` in a base commit
  browseros-ai#1421  carried by a commit that says "Carries the browseros-ai#1421 work it belongs with"

Nine conflicting branches became six. The matching is done in JavaScript rather
than in git's regex, because two rounds ago a BRE read as a JavaScript regex
convicted a bee - the dialect belongs somewhere it is known.

AND THE TIGHTENING BROKE THE CASE IT WAS WRITTEN FOR. I required `(#N)` to end
the line, to reject "unlike (browseros-ai#1421), this does X". One minute later browseros-ai#1310 stopped
being recognised: a squash subject here reads
`feat(queen): explain idle paid slots (browseros-ai#1310) (#330)` - the issue first, then the
pull request. A trailing CHAIN of references is a subject; a parenthesis in the
middle of a sentence is not.

CLOSE-DONE KEPT ITS OWN COPY OF THE RULE. It had the tree test and nothing else,
for weeks, while `land.mjs` grew four more routes it never learned. A rule
transcribed twice is two rules that agree until somebody edits one - which is L2
of this repository, and it had happened here in the file that decides whether an
issue may be closed. It asks `land.mjs` now.

What remains is real debt and is reported as such: browseros-ai#1387, browseros-ai#1302 and browseros-ai#1303 carry
880 insertions of finished work outside the base, all three with their issues
already CLOSED - the inverse of the false statement close-done exists to prevent.
A conflict is still reported for a person and never resolved by guessing.

selftest 144 pass 0 fail.

Co-authored-by: Dmitrii Vasilev <trackgmedernj@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant