Repository navigation
fix(cli): agent run refuses an --adapter that differs from the token … #79
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish npm packages | |
| # Companion to package-version-guard.yml. That guard makes "source moved but | |
| # version didn't" go red on the PR; this workflow closes the other half of the | |
| # loop: "version moved but nobody published". As of 2026-08-29 main carried | |
| # cli 0.1.22 / mcp 0.3.5 while npm served 0.1.18 / 0.3.4 — four CLI releases | |
| # that never left the repo, so every `commonly` install and every seat on this | |
| # laptop ran code the repo had already replaced. | |
| # | |
| # Trigger: a push to main that touches a published package. For each package, | |
| # compare package.json's version with what the registry serves: | |
| # equal → nothing to do (most pushes) | |
| # repo > registry → publish, then read it back from the registry | |
| # repo < registry → fail loudly; the repo walked backwards (see the guard) | |
| # | |
| # Auth: npm trusted publishing (OIDC). No token is stored anywhere. Each package | |
| # on npmjs.com names this repo and this workflow file as its trusted publisher, | |
| # and the job's id-token is exchanged for a short-lived publish credential. | |
| # That needs npm >= 11.5.1, which the job installs, and `id-token: write`. | |
| # Provenance is attached automatically. | |
| # | |
| # Why: until 2026-10-01 this used an NPM_TOKEN granular token (set 2026-08-29). | |
| # It stopped being accepted, and the cli 0.1.82 publish failed with | |
| # `E404 Not Found - PUT .../@commonlyai%2fcli` (run 36872618263): a token that | |
| # can expire is a release outage on a timer. | |
| # | |
| # This file is deliberately NOT in its own path filter: merging it must not | |
| # fire a publish before the trusted publisher is configured on npmjs.com. | |
| # The first release after the switch goes through workflow_dispatch. | |
| on: | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - 'cli/**' | |
| - 'commonly-mcp/**' | |
| - 'docs/agents/skills/commonly/SKILL.md' # cli prepublishOnly copies it in | |
| workflow_dispatch: | |
| inputs: | |
| package: | |
| description: 'Package dir to publish (cli | commonly-mcp | all)' | |
| default: 'all' | |
| type: choice | |
| options: [ all, cli, commonly-mcp ] | |
| permissions: | |
| contents: read | |
| id-token: write # npm trusted publishing (OIDC) + provenance | |
| concurrency: | |
| group: npm-publish | |
| cancel-in-progress: false # never abort a publish mid-flight | |
| jobs: | |
| publish: | |
| name: ${{ matrix.pkg }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| pkg: [ cli, commonly-mcp ] | |
| # No job-level `if` on matrix: jobs.<id>.if cannot see the matrix context | |
| # (only github/needs/vars/inputs), and a workflow that fails validation | |
| # never appears in the check set — it is absent, not red. The package | |
| # picker is applied in the compare step instead. | |
| defaults: | |
| run: | |
| working-directory: ${{ matrix.pkg }} | |
| env: | |
| PKG: ${{ matrix.pkg }} | |
| PICK: ${{ github.event.inputs.package || 'all' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # No `registry-url`: setup-node@v4 then writes | |
| # `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` to the runner npmrc, | |
| # and npm reads a present-but-empty token as "auth configured", skips the | |
| # OIDC exchange and fails E404 (Vera, measured in setup-node's dist; fixed | |
| # only in v7). npmjs.org is npm's default registry anyway. | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Compare repo version with the registry | |
| id: cmp | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| name=$(node -p "require('./package.json').name") | |
| repo_v=$(node -p "require('./package.json').version") | |
| echo "name=$name" >> "$GITHUB_OUTPUT" | |
| echo "repo_v=$repo_v" >> "$GITHUB_OUTPUT" | |
| if [ "$PICK" != "all" ] && [ "$PICK" != "$PKG" ]; then | |
| echo "· $name: not selected (package=$PICK)" | |
| echo "action=skip" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # "Never published" (E404) is the first-release path and maps to | |
| # 0.0.0. Anything else — outage, auth, rate limit — must stop the | |
| # run: a registry we cannot read is not a registry at 0.0.0. | |
| if reg_v=$(npm view "$name" version 2>/tmp/npm-view.err); then | |
| : | |
| elif grep -q 'E404' /tmp/npm-view.err; then | |
| reg_v="0.0.0" | |
| else | |
| echo "::error::$name: npm view failed for a reason other than E404:"; cat /tmp/npm-view.err | |
| exit 1 | |
| fi | |
| echo "reg_v=$reg_v" >> "$GITHUB_OUTPUT" | |
| if [ "$repo_v" = "$reg_v" ]; then | |
| echo "· $name: registry already at $reg_v" | |
| echo "action=skip" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Prerelease ordering is beyond `sort -V` (same limit the version | |
| # guard documents). Checked AFTER equality so a repo and registry | |
| # parked on the same prerelease is a no-op, not a failure. | |
| case "$repo_v$reg_v" in *-*) | |
| echo "::error::$name: prerelease in $reg_v → $repo_v; this workflow does not order prereleases. Publish by hand." | |
| exit 1;; | |
| esac | |
| if [ "$(printf '%s\n%s\n' "$reg_v" "$repo_v" | sort -V | tail -1)" = "$repo_v" ]; then | |
| echo "✓ $name: $reg_v → $repo_v, publishing" | |
| echo "action=publish" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::$name: repo $repo_v is BELOW registry $reg_v. main walked the version backwards; fix the repo, do not republish." | |
| exit 1 | |
| fi | |
| # Trusted publishing needs npm >= 11.5.1; Node 22 ships npm 10. | |
| - name: Use an npm that supports trusted publishing | |
| if: steps.cmp.outputs.action == 'publish' | |
| run: npm install -g npm@^11.5.1 && npm --version | |
| # Turns the opaque registry E404 into a named failure (Vera 76338). | |
| - name: Refuse to publish without a usable OIDC identity | |
| if: steps.cmp.outputs.action == 'publish' | |
| run: | | |
| [ -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ] || { echo "::error::id-token: write is not in effect; trusted publishing cannot run"; exit 1; } | |
| for f in "${NPM_CONFIG_USERCONFIG:-}" "$HOME/.npmrc" ./.npmrc; do | |
| [ -n "$f" ] && [ -f "$f" ] && grep -q '_authToken' "$f" && { echo "::error::$f carries _authToken; npm would skip the OIDC exchange and fail E404"; exit 1; } | |
| done | |
| true | |
| - name: Install | |
| if: steps.cmp.outputs.action == 'publish' | |
| run: npm ci --ignore-scripts | |
| - name: Publish | |
| if: steps.cmp.outputs.action == 'publish' | |
| # No NODE_AUTH_TOKEN: npm exchanges this job's OIDC id-token with the | |
| # registry, because the package names this workflow as trusted publisher. | |
| run: npm publish --provenance --access public | |
| # The poll, its budget (READBACK_TIMEOUT_SECONDS; the script prints the | |
| # value in force, which is the single place this number is stated) and the | |
| # failure diagnostics live in the script, not inline here — a workflow | |
| # body cannot be exercised by the suite, and this loop went red on two | |
| # good publishes (cli 0.1.65 run 35498255344, 0.1.66 run 35804128707) | |
| # before anyone could test it. TASK-100. | |
| # | |
| # $GITHUB_WORKSPACE, not a relative path: this job sets | |
| # working-directory to the package dir, and the script is at the repo | |
| # root. It is cwd-independent, so the workspace path is the only | |
| # unambiguous way to reach it. | |
| - name: Read it back from the registry | |
| if: steps.cmp.outputs.action == 'publish' | |
| shell: bash | |
| env: | |
| NAME: ${{ steps.cmp.outputs.name }} | |
| WANT: ${{ steps.cmp.outputs.repo_v }} | |
| run: bash "$GITHUB_WORKSPACE/scripts/verify-npm-publish-readback.sh" |