Skip to content

build(release): check publish credentials before packing - #22

Merged
anvanster merged 3 commits into
mainfrom
chore/publish-auth-upfront
Sep 13, 2026
Merged

anvanster merged 3 commits into
mainfrom
chore/publish-auth-upfront

Conversation

@anvanster

Copy link
Copy Markdown
Member

Intent

The developer asked to commit a pending local edit to scripts/package-npm.sh and push it through the no-mistakes gate. The intent behind that edit, stated earlier in the session, was to move the registry authentication steps - mcp-publisher login github -token "$(gh auth token)" and an npm login check - to the head of the packaging script, so that a stale or missing credential fails immediately instead of after several minutes of tests, engine-asset probing and packing (the failure mode that cost time on the previous release). Constraints agreed during the work: the auth block must be gated on --publish so the pack-only path used by plain builds and the validation gate never prompts and hangs; npm login should run only when npm whoami already fails, leaving an existing session alone and staying interactive because of the account's 2FA; and gh auth token is used for the MCP Registry because it carries the read:org scope the registry needs, with CODEGRAPH_MCP_TOKEN as an override for a narrower PAT. The change was made after 0.20.1 had already been published manually, so it affects only future releases. Per the user's standing instruction, the push must go through git push no-mistakes rather than directly to origin.

What Changed

  • scripts/package-npm.sh --publish now runs a credential preflight before the tests, engine-asset probe and pack: it reports the current npm whoami identity or starts an interactive npm login (the account has 2FA), so a dead npm session aborts the run in seconds rather than after several minutes of work. The pack-only path is untouched and still needs no credentials.
  • The MCP Registry precondition is checked against GitHub directly instead of inferred from a successful login: the script calls GET /user and, when the namespace in mcp-package/server.json is not the token's own user namespace, GET /user/memberships/orgs, requiring role/state of admin/active for the owning org. A 403 on the memberships call is reported as a missing read:org scope, and a token is taken from CODEGRAPH_MCP_TOKEN when set, otherwise gh auth token. A missing mcp-publisher binary warns rather than blocks.
  • The Registry token itself is still minted at the publish step, since it lives 300 seconds - shorter than the rest of the script - and mcp-publisher login is now error-checked there, printing its output plus the two-command recovery for the case where npmjs.com already has the version but the Registry does not.

Pipeline review left informational notes on diagnostics in the new auth block (GitHub's body is not echoed on the memberships 403; curl ... || echo 000 can print the status twice on a transport failure) and lint flagged a pre-existing SC2012/SC2035 on the untouched tarball lookup at line 299.

Risk Assessment

✅ Low: The round-1 error is genuinely fixed - the 300-second MCP JWT is now minted immediately before mcp-publisher publish and the up-front gate checks the long-lived GitHub credential instead, with the ownership preflight verified against the live API (role admin / state active for codegraph-ai) so it neither false-passes nor false-blocks; only two diagnostic-message refinements remain in a manually run release script.

Testing

I drove the real scripts/package-npm.sh unmodified through 11 end-to-end scenarios with test doubles for npm, gh, mcp-publisher and api.github.com, so nothing was ever published; the package's own npm test (fetch-engine + wrapper-args) ran inside every pack-path scenario and passed, and the release-asset probe hit the real GitHub release. The decisive evidence is each transcript's trailing list of commands the script actually invoked: on every credential-failure path it aborts in under a second with npm test, npm pack and npm publish absent, and on the pack-only path the credential block never runs at all. One scenario checked the preflight against the live api.github.com with the maintainer's real gh token, confirming the actual release credential passes the org-ownership gate. No automated test exists in the repo for this release-time shell script, so this was manual verification captured as CLI transcripts; the change has no UI surface - the terminal output is the end-user experience - so no screenshot applies. Transient tarballs created by the pack step were deleted and the worktree is clean.

Evidence: All 11 scenario transcripts (combined)
# scripts/package-npm.sh - publish-credential preflight, end to end

The real script is run unmodified. npm, gh, mcp-publisher and api.github.com
are test doubles that only record what the script asked them to do, so no
package and no server entry is ever published. Scenario 02 is the exception:
there the GitHub preflight runs against the live api.github.com with the
maintainer's real gh token.

Each transcript ends with the list of commands the script actually invoked,
which is what shows the ordering: on every failure path 'npm test', 'npm pack'
and 'npm publish' are absent.

`` `
### Pack-only build with NO usable credentials: the auth block must not run
$ ./scripts/package-npm.sh 
=== CodeGraph npm package builder ===

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

To publish: cd mcp-package && npm publish --access public
Then update MCP Registry: mcp-publisher publish --server-json server.json

[exit code 0 after 4.1s]
[commands the script actually invoked:]
  npm test
  npm pack
`` `

`` `
### Publish path, real GitHub token checked against the live api.github.com (npm + mcp-publisher stubbed)
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

Publishing to npmjs.com...
npm notice (stub) publish publish --access public
+ @astudioplus/codegraph-mcp@0.20.1
✓ Published @astudioplus/codegraph-mcp@0.20.1

Updating MCP Registry...
(stub) published server.json
✓ MCP Registry updated

[exit code 0 after 4.9s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  npm test
  npm pack
  npm publish --access public
  mcp-publisher login github -token <redacted>
  mcp-publisher publish --server-json server.json
`` `

`` `
### Stale GitHub token: must fail before the tests, the asset probe and the pack
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✗ GitHub rejected the token (HTTP 401) - not packaging
    {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}
    Run 'gh auth login', or set CODEGRAPH_MCP_TOKEN to a live PAT with read:org.

[exit code 1 after 0.0s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  curl https://api.github.com/user (mode=bad-token)
`` `

`` `
### PAT without read:org: the failure the MCP Registry would otherwise report as a bogus membership error
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✗ the GitHub token cannot read organisation membership - not packaging
    GitHub answered 403 for GET /user/memberships/orgs, which needs read:org.
    Without it the Registry sees no organisations and refuses io.github.codegraph-ai.
    Use 'gh auth token', or set CODEGRAPH_MCP_TOKEN to a PAT with read:org.

[exit code 1 after 0.1s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  curl https://api.github.com/user (mode=no-readorg)
  curl https://api.github.com/user/memberships/orgs?per_page=100 (mode=no-readorg)
`` `

`` `
### Token whose account is a plain member of codegraph-ai, not an owner
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✗ anvanster does not own the codegraph-ai organisation - not packaging
    GET /user/memberships/orgs reports role/state: member/active
    The Registry grants io.github.codegraph-ai to owners (role admin) only.

[exit code 1 after 0.2s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  curl https://api.github.com/user (mode=not-owner)
  curl https://api.github.com/user/memberships/orgs?per_page=100 (mode=not-owner)
`` `

`` `
### No GitHub token at all (gh not logged in, no CODEGRAPH_MCP_TOKEN)
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✗ no GitHub token for the MCP Registry - not packaging
    Run 'gh auth login', or set CODEGRAPH_MCP_TOKEN to a PAT with read:org.

[exit code 1 after 0.0s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
`` `

`` `
### CODEGRAPH_MCP_TOKEN overrides gh (note: 'gh auth token' is never called)
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

Publishing to npmjs.com...
npm notice (stub) publish publish --access public
+ @astudioplus/codegraph-mcp@0.20.1
✓ Published @astudioplus/codegraph-mcp@0.20.1

Updating MCP Registry...
(stub) published server.json
✓ MCP Registry updated

[exit code 0 after 5.9s]
[commands the script actually invoked:]
  npm whoami
  curl https://api.github.com/user (mode=owner)
  curl https://api.github.com/user/memberships/orgs?per_page=100 (mode=owner)
  npm test
  npm pack
  npm publish --access public
  mcp-publisher login github -token <redacted>
  mcp-publisher publish --server-json server.json
`` `

`` `
### No npm session: npm login runs up-front, before the pack
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  npm: not logged in - starting login (2FA expected)
npm notice Log in on https://registry.npmjs.org/
Login at: https://www.npmjs.com/login?next=/login/cli/... (2FA prompt)
Logged in on https://registry.npmjs.org/.
  ✓ npm authenticated as anvanster
  ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

Publishing to npmjs.com...
npm notice (stub) publish publish --access public
+ @astudioplus/codegraph-mcp@0.20.1
✓ Published @astudioplus/codegraph-mcp@0.20.1

Updating MCP Registry...
(stub) published server.json
✓ MCP Registry updated

[exit code 0 after 3.9s]
[commands the script actually invoked:]
  npm whoami
  npm login
  npm whoami
  curl https://api.github.com/user (mode=owner)
  curl https://api.github.com/user/memberships/orgs?per_page=100 (mode=owner)
  npm test
  npm pack
  npm publish --access public
  mcp-publisher login github -token <redacted>
  mcp-publisher publish --server-json server.json
`` `

`` `
### npm login fails (2FA not supplied): stop before doing any work
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  npm: not logged in - starting login (2FA expected)
npm error code EOTP
npm error This operation requires a one-time password, and it was not supplied.
  ✗ npm login failed - not packaging

[exit code 1 after 0.0s]
[commands the script actually invoked:]
  npm whoami
  npm login
`` `

`` `
### mcp-publisher not installed: warn, do not block the release
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ⚠ mcp-publisher not on PATH - the MCP Registry step will be skipped

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

Publishing to npmjs.com...
npm notice (stub) publish publish --access public
+ @astudioplus/codegraph-mcp@0.20.1
✓ Published @astudioplus/codegraph-mcp@0.20.1

Updating MCP Registry...
⚠ mcp-publisher not found — update the MCP Registry manually:
  cd mcp-package && mcp-publisher publish --server-json server.json

[exit code 0 after 3.6s]
[commands the script actually invoked:]
  npm whoami
  npm test
  npm pack
  npm publish --access public
`` `

`` `
### MCP token expires between preflight and publish: npm is already published, so the script says exactly what is left to do
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

Publishing to npmjs.com...
npm notice (stub) publish publish --access public
+ @astudioplus/codegraph-mcp@0.20.1
✓ Published @astudioplus/codegraph-mcp@0.20.1

Updating MCP Registry...
Error: failed to exchange GitHub token: 401 Unauthorized
✗ mcp-publisher login failed - npmjs.com has 0.20.1 but the MCP
  Registry does not. Nothing else is needed; re-run just that step:
  cd mcp-package && mcp-publisher login github -token "$(gh auth token)" \
    && mcp-publisher publish --server-json server.json

[exit code 1 after 3.8s]
[commands the script actually invoked:]
  npm whoami
  curl https://api.github.com/user (mode=owner)
  curl https://api.github.com/user/memberships/orgs?per_page=100 (mode=owner)
  npm test
  npm pack
  npm publish --access public
  mcp-publisher login github -token <redacted>
`` `
Evidence: Fail-fast: stale GitHub token aborts before the tests, asset probe and pack

$ ./scripts/package-npm.sh --publish === CodeGraph npm package builder === Checking publish credentials... ✓ npm authenticated as anvanster ✗ GitHub rejected the token (HTTP 401) - not packaging {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest&#34;,&#34;status&#34;:&#34;401&#34;}&#10;Run 'gh auth login', or set CODEGRAPH_MCP_TOKEN to a live PAT with read:org. [exit code 1 after 0.0s] [commands the script actually invoked:] npm whoami gh auth token curl https://api.github.com/user (mode=bad-token)

### Stale GitHub token: must fail before the tests, the asset probe and the pack
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✗ GitHub rejected the token (HTTP 401) - not packaging
    {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}
    Run 'gh auth login', or set CODEGRAPH_MCP_TOKEN to a live PAT with read:org.

[exit code 1 after 0.0s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  curl https://api.github.com/user (mode=bad-token)
Evidence: Publish path with the real gh token against live api.github.com (npm/mcp-publisher stubbed)

$ ./scripts/package-npm.sh --publish === CodeGraph npm package builder === Checking publish credentials... ✓ npm authenticated as anvanster ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable Removing any bundled binaries (the engine is fetched at install time)... Checking the engine fetch and the wrapper arguments... ✓ package tests pass ... ✓ every engine asset is published for v0.20.1 Packing... ✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz Publishing to npmjs.com... ✓ Published @astudioplus/codegraph-mcp@0.20.1 Updating MCP Registry... ✓ MCP Registry updated [exit code 0 after 4.9s] [commands the script actually invoked:] npm whoami gh auth token npm test npm pack npm publish --access public mcp-publisher login github -token <redacted> mcp-publisher publish --server-json server.json

### Publish path, real GitHub token checked against the live api.github.com (npm + mcp-publisher stubbed)
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

Publishing to npmjs.com...
npm notice (stub) publish publish --access public
+ @astudioplus/codegraph-mcp@0.20.1
✓ Published @astudioplus/codegraph-mcp@0.20.1

Updating MCP Registry...
(stub) published server.json
✓ MCP Registry updated

[exit code 0 after 4.9s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  npm test
  npm pack
  npm publish --access public
  mcp-publisher login github -token <redacted>
  mcp-publisher publish --server-json server.json
Evidence: PAT without read:org gets the accurate diagnostic instead of the registry's bogus membership error

Checking publish credentials... ✓ npm authenticated as anvanster ✗ the GitHub token cannot read organisation membership - not packaging GitHub answered 403 for GET /user/memberships/orgs, which needs read:org. Without it the Registry sees no organisations and refuses io.github.codegraph-ai. Use 'gh auth token', or set CODEGRAPH_MCP_TOKEN to a PAT with read:org. [exit code 1 after 0.1s]

### PAT without read:org: the failure the MCP Registry would otherwise report as a bogus membership error
$ ./scripts/package-npm.sh --publish
=== CodeGraph npm package builder ===

Checking publish credentials...
  ✓ npm authenticated as anvanster
  ✗ the GitHub token cannot read organisation membership - not packaging
    GitHub answered 403 for GET /user/memberships/orgs, which needs read:org.
    Without it the Registry sees no organisations and refuses io.github.codegraph-ai.
    Use 'gh auth token', or set CODEGRAPH_MCP_TOKEN to a PAT with read:org.

[exit code 1 after 0.1s]
[commands the script actually invoked:]
  npm whoami
  gh auth token
  curl https://api.github.com/user (mode=no-readorg)
  curl https://api.github.com/user/memberships/orgs?per_page=100 (mode=no-readorg)
Evidence: Pack-only build with no usable credentials never enters the auth block

$ ./scripts/package-npm.sh === CodeGraph npm package builder === Removing any bundled binaries (the engine is fetched at install time)... (no "Checking publish credentials" line - no prompt, no hang) ... ✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz [exit code 0 after 4.1s] [commands the script actually invoked:] npm test npm pack

### Pack-only build with NO usable credentials: the auth block must not run
$ ./scripts/package-npm.sh 
=== CodeGraph npm package builder ===

Removing any bundled binaries (the engine is fetched at install time)...

Checking the engine fetch and the wrapper arguments...
  ✓ package tests pass

package.json version:      0.20.1
server.json version:       0.20.1
server.json npm package:   0.20.1

engine version:            0.20.1 (fetched at install time)
Checking published engine assets for v0.20.1...
  ✓ codegraph-server-darwin-arm64
  ✓ codegraph-server-darwin-x64
  ✓ codegraph-server-linux-arm64
  ✓ codegraph-server-linux-x64
  ✓ codegraph-server-win32-x64.exe
  ✓ onnxruntime.dll
  ✓ every engine asset is published for v0.20.1

Packing...
npm notice (stub) created astudioplus-codegraph-mcp-0.20.1.tgz

✓ Created: mcp-package/astudioplus-codegraph-mcp-0.20.1.tgz (4.0K)

To publish: cd mcp-package && npm publish --access public
Then update MCP Registry: mcp-publisher publish --server-json server.json

[exit code 0 after 4.1s]
[commands the script actually invoked:]
  npm test
  npm pack
Evidence: Test harness (reproducible)
#!/bin/bash
# Drive the real scripts/package-npm.sh under test doubles for npm / gh /
# mcp-publisher / api.github.com. Nothing here is published: the npm and
# mcp-publisher doubles only record what the script asked them to do.
#
# usage: run-scenario.sh <name> <title> [--publish]
set -uo pipefail
EV="$(cd "$(dirname "$0")" && pwd)"
REPO="${REPO:?set REPO to the worktree root}"
name="$1"; title="$2"; shift 2

export STUB_DIR="$EV/state/$name"; rm -rf "$STUB_DIR"; mkdir -p "$STUB_DIR"
export STUB_LOG="$STUB_DIR/calls.log"; : > "$STUB_LOG"
[ "${NPM_LOGGED_IN:-1}" = "1" ] && printf '%s' "${STUB_NPM_USER:-anvanster}" > "$STUB_DIR/npm-session"

PATH="$EV/stub-bin:$(dirname "$(command -v node)"):/usr/bin:/bin:/usr/sbin:/sbin"
[ "${WITH_MCP_PUBLISHER:-1}" = "1" ] && PATH="$EV/stub-bin-mcp:$PATH"
export PATH

t0="$(perl -MTime::HiRes=time -e 'printf "%.3f", time')"
out="$(cd "$REPO" && ./scripts/package-npm.sh "$@" 2>&1)"; rc=$?
t1="$(perl -MTime::HiRes=time -e 'printf "%.3f", time')"

{
  echo "### $title"
  echo "\$ ./scripts/package-npm.sh $*"
  echo "$out"
  echo
  echo "[exit code $rc after $(perl -e "printf '%.1f', $t1-$t0")s]"
  echo "[commands the script actually invoked:]"
  sed 's/^/  /' "$STUB_LOG" | sed -E 's/(-token )[A-Za-z0-9_]+/\1<redacted>/'
  [ -s "$STUB_LOG" ] || echo "  (none)"
} | sed -E "s/$(printf '%s' "${STUB_GH_TOKEN:-__nomatch__}")/<token>/g" | tee "$EV/transcripts/$name.txt"

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 2 infos
  • 🚨 scripts/package-npm.sh:73 - The MCP Registry JWT minted by mcp-publisher login lives 5 minutes, so moving the login to the head of the script guarantees it is expired by the time mcp-publisher publish runs at line 233. Verified against the real credential issued by registry.modelcontextprotocol.io: the stored JWT has exp - iat == 300, and the credential file holds only {method, registry, token} - no GitHub token, so the publisher cannot silently refresh it. Between line 73 and line 233 the script runs the package tests (line 107), ~12 curl probes against the GitHub release (line 190), npm pack (line 215) and npm publish (line 228), which on this 2FA account blocks on a human OTP prompt of unbounded duration. Failure sequence: login succeeds and prints '✓ mcp-publisher authenticated', npm publish succeeds (irreversible for that version), then mcp-publisher publish 401s on an expired token - the exact 'fails at the very end, after everything else passed' outcome the commit exists to remove, now made near-deterministic rather than occasional. Fix: keep the up-front check as a fail-fast probe, but re-run mcp-publisher login github -token &#34;$MCP_TOKEN&#34; immediately before line 233. MCP_TOKEN is already set in the same shell (line 68) and is still in scope there, so this is additive and preserves the commit's intent.
  • ⚠️ scripts/package-npm.sh:74 - The up-front gate cannot detect the failure it documents. Per the comment at lines 57-62 (confirmed by the issued token's permissions claim, which is a namespace list: [io.github.anvanster/, io.github.codegraph-ai/]), a token missing read:org does not fail login - the registry treats GitHub's 403 on /user/memberships/orgs as 'no admin orgs' and issues a valid JWT scoped only to io.github.<user>/*. So mcp-publisher login returns 0, line 74 prints '✓ mcp-publisher authenticated', and the 403 still arrives at line 233 when publishing io.github.codegraph-ai/codegraph. Conversely the read:org hint at lines 77-78 is printed only on the login-failure branch, which is the one case where read:org is not the cause. To actually gate on this, verify the scope before trusting the login - e.g. probe gh api user/memberships/orgs (or curl api.github.com with the token) and fail on 403, or decode the permissions claim of the stored token and assert it covers the namespace in mcp-package/server.json (io.github.codegraph-ai/codegraph).
  • ℹ️ scripts/package-npm.sh:73 - mcp-publisher login ... &gt;/dev/null 2&gt;&amp;1 discards the tool's own diagnostic, so a login failure caused by an unreachable registry, a revoked token or a malformed PAT is reported only as the fixed read:org/org-ownership hint at lines 77-78. Capture the output (login_log=$(mcp-publisher login github -token &#34;$MCP_TOKEN&#34; 2&gt;&amp;1)) and print it to stderr on the failure branch alongside the hint, matching how the package-test failure at lines 107-110 replays test_log.
  • ℹ️ scripts/package-npm.sh:50 - npm whoami is invoked up to three times (lines 49, 50, 54), each a separate registry round trip, and the value printed at line 50 is re-fetched rather than reused from the line 49 probe. Capture it once (if npm_user=$(npm whoami 2&gt;/dev/null); then ...) and reuse the variable in both the already-logged-in and post-login branches.

🔧 Fix: mint MCP token at publish, preflight GitHub org ownership
2 infos still open:

  • ℹ️ scripts/package-npm.sh:129 - The 403 branch asserts a single cause without showing GitHub's own message: unlike the generic failure at lines 136-139, it never calls gh_body_err. GET /user/memberships/orgs also returns 403 when the token is valid and read:org-scoped but not SSO-authorised for the org, or when an org policy blocks the PAT; in those cases the operator is told to get a token with read:org, which they already have. That is the same 'confident message about a cause that is not the actual cause' failure this change exists to remove, just relocated. Add gh_body_err after line 130 so GitHub's body (e.g. the SAML enforcement URL) is printed alongside the read:org hint.
  • ℹ️ scripts/package-npm.sh:97 - curl ... -w &#39;%{http_code}&#39; ... || echo 000 emits the status twice on a transport failure: curl already writes 000 from -w before exiting non-zero, so the fallback appends a second one. Verified locally - curl -sS -o /dev/null -w &#39;%{http_code}&#39; http://127.0.0.1:9/ || echo 000 yields 000000, which surfaces as 'GitHub rejected the token (HTTP 000000)' at line 105 or 'HTTP 000000' at line 137. The comparisons still fail closed, so this is diagnostic only. Also, 2&gt;/dev/null at line 97 discards curl's -S error text, so the underlying reason (DNS, TLS, proxy) is lost. Fix: drop the || echo 000, normalise with code=&#34;${code:-000}&#34;, and keep curl's stderr for the failure path.
✅ **Test** - passed

✅ No issues found.

  • ./scripts/package-npm.sh (pack-only, no credentials available) - auth block skipped entirely, exits 0; command log shows only npm test and npm pack
  • ./scripts/package-npm.sh --publish with the maintainer's real gh auth token against the live api.github.com - preflight prints ✓ anvanster owns codegraph-ai - io.github.codegraph-ai is publishable before the tests/asset probe/pack
  • ./scripts/package-npm.sh --publish with a stale GitHub token (api.github.com/user -> 401) - aborts in 0.0s, no npm test / npm pack / npm publish invoked
  • ./scripts/package-npm.sh --publish with a PAT lacking read:org (GET /user/memberships/orgs -> 403) - prints the read:org diagnostic and stops
  • ./scripts/package-npm.sh --publish with a plain (non-owner) codegraph-ai membership - refused with role/state: member/active
  • ./scripts/package-npm.sh --publish with no gh token and no CODEGRAPH_MCP_TOKEN - refused with the gh auth login hint
  • CODEGRAPH_MCP_TOKEN=... ./scripts/package-npm.sh --publish - override used, gh auth token never called
  • ./scripts/package-npm.sh --publish with no npm session - npm login runs up-front, before the pack; with a session present npm login is never invoked
  • ./scripts/package-npm.sh --publish with a failing npm login (EOTP) - stops before any work
  • ./scripts/package-npm.sh --publish with mcp-publisher off PATH - warns at preflight and at the registry step, release not blocked
  • ./scripts/package-npm.sh --publish with mcp-publisher login failing after npm publish - prints the two-command recovery and exits 1
  • cd mcp-package &amp;&amp; npm test (fetch-engine + wrapper-args) - run as part of every pack-path scenario, passes
  • git status --porcelain after the run - clean, transient mcp-package/*.tgz removed
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 info
  • ℹ️ scripts/package-npm.sh:299 - shellcheck reports SC2012/SC2035 on the pre-existing tarball lookup TARBALL=$(ls -t *.tgz 2&gt;/dev/null | head -1). It predates this change and is untouched by it, so fixing it would mean altering unrelated packaging behavior; left as-is and reported for visibility.
✅ **Push** - passed

✅ No issues found.

anvanster and others added 3 commits September 12, 2026 22:45
A stale credential used to surface at the very end of a publish. The last
release spent several minutes on tests, the engine-asset probe and the pack
before `npm publish` failed on an expired session, and `mcp-publisher` failed
after that - so every one of those steps had to be repeated.

Both logins now run first. Each has to succeed for the publish to happen
anyway, so checking them up front costs nothing and turns a late failure into
an immediate one.

Gated on --publish. Packing needs no credentials, and this script also runs as
a plain build step and inside the validation gate, where prompting for a login
would hang it. npm is only prompted for when `npm whoami` already fails, so an
existing session is left alone; it stays interactive because the account has
2FA.

The mcp-publisher login uses `gh auth token` because the MCP Registry decides
which namespaces a token may publish to by calling GET /user/memberships/orgs,
which needs the read:org scope. Its own device flow mints a token without that
scope, GitHub answers 403, and the registry treats the 403 as "no admin orgs"
rather than an error - so publishing silently degrades to io.github.<user>/*
and then fails on io.github.codegraph-ai/* with a message blaming organization
membership, which is not the cause. That cost a release cycle to diagnose, so
the reasoning is recorded next to the call. CODEGRAPH_MCP_TOKEN overrides it
for anyone preferring a PAT scoped to read:org alone, since gh's token also
carries repo and workflow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rVbt7rENTwXkdHt3Bpgb5
@github-actions

Copy link
Copy Markdown

🔍 CodeGraph PR Review

1 files changed (+153/−0, 2 functions) · Risk: 🟢 low

⚠️ Test gaps (2 functions, 0 coverage)

  • gh_body_err (scripts/package-npm.sh) — signature_changed
  • gh_api (scripts/package-npm.sh) — signature_changed

Suggested reviewers

Andrey Vasilevsky (5 lines), anvanster (3 lines)

Suggested commit: feat(core): <describe the change> · 0 tests cover the changes
🤖 Generated by CodeGraph

@anvanster
anvanster merged commit 64f4f30 into main Sep 13, 2026
1 check passed
@anvanster
anvanster deleted the chore/publish-auth-upfront branch September 13, 2026 06:09
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