Skip to content

fix: unblock the release and make the registry publish retryable - #26

Merged
wittjosiah merged 4 commits into
mainfrom
claude/plugin-excalidraw-pr8-cleanup-hb0ebu
Aug 8, 2026
Merged

fix: unblock the release and make the registry publish retryable#26
wittjosiah merged 4 commits into
mainfrom
claude/plugin-excalidraw-pr8-cleanup-hb0ebu

Conversation

@wittjosiah

@wittjosiah wittjosiah commented Aug 8, 2026

Copy link
Copy Markdown
Member

The 0.10.1 release failed at the npm publish (run 31265246246), and in diagnosing it a second problem surfaced: the registry half could not be retried at all.

1. The publish failure

E422 Unprocessable Entity - PUT https://registry.npmjs.org/@dxos%2fplugin-tictactoe
Error verifying sigstore provenance bundle: Failed to validate repository information:
package.json: "repository.url" is "", expected to match "https://github.com/dxos/plugins" from provenance

release.yml sets NPM_CONFIG_PROVENANCE: 'true', so npm signs a statement naming the source repo and validates it against the published package's repository.url. packages/tictactoe/package.json has no repository field, so the comparison is against "".

Nothing warns first — npm signed and wrote the statement to the transparency log (logIndex=2386003534) before the registry rejected the tarball.

  • packages/tictactoe/package.json — add repository (git+https://github.com/dxos/plugins.git, directory: packages/tictactoe).
  • scripts/check-packages-published.mjs — fail a publishable plugin whose repository.url is missing or doesn't match GITHUB_REPOSITORY, alongside the existing never-published check. Runs before the network calls, so it fails fast and works offline; locally it checks presence only.

2. The registry publish could not be retried

The registry step was gated on steps.changesets.outputs.published == 'true' and fed by publishedPackages — both of which only exist in the run that published to npm. npm is append-only, so once a version is accepted, changeset publish skips it, reports nothing released, and the registry step never runs again. The half that talks to two external services (DXOS edge + the publisher's PDS) was the half with no retry path, short of burning a version.

  • release.yml — a registry_only dispatch input republishes the current versions to the registry and skips npm.
  • scripts/release-dirs.mjs — the selection moves out of an inline node -e blob into a script, so both modes are testable: ALL=true takes every publishable plugin, PUBLISHED names the set otherwise.

I left the channel order alone. Registry-first would also have avoided the trap, but a retry switch fixes it in both directions and costs less churn.

Verification

check-packages-published, both failure paths — a mismatch-only guard would have missed this bug, which was an absent field:

node scripts/check-packages-published.mjs                    → OK,   exit 0
GITHUB_REPOSITORY=dxos/plugins  …                            → OK,   exit 0
GITHUB_REPOSITORY=dxos/wrong    …                            → FAIL, exit 1  (mismatch)
repository removed, GITHUB_REPOSITORY=dxos/plugins           → FAIL, exit 1  (absent)

release-dirs.mjs, every input shape the workflow can hand it:

ALL=true                                    → packages/tictactoe
PUBLISHED=[{"name":"@dxos/plugin-tictactoe"}] → packages/tictactoe
PUBLISHED=[]                                → empty
PUBLISHED unset                             → empty
PUBLISHED=[{"name":"@dxos/plugin-other"}]   → empty
PUBLISHED='not json'                        → FAIL, exit 1

Workflow parsed and step conditions asserted: changesets gains if: ${{ !inputs.registry_only }}, the registry step if: ${{ inputs.registry_only || steps.changesets.outputs.published == 'true' }}. On push the inputs context is empty, so both evaluate as before.

moon run :lint clean, pnpm format applied.

Two things worth knowing

npm only holds a 0.0.0 placeholder for @dxos/plugin-tictactoedist-tags.latest is 0.0.0. check-packages-published passes because it only asks whether the package exists, which is the right question for its purpose (trusted-publisher configuration), but no real version has ever shipped. 0.10.1 will be the first.

No changeset, deliberately. 0.10.1 is bumped on main with its changesets consumed but never published, so merging this leaves main at 0.10.1 with nothing pending, and release.yml goes straight to the publish path and retries. A changeset would bump to 0.10.2 and strand 0.10.1.

The 0.10.1 release failed at the npm publish:

  E422 Unprocessable Entity - PUT https://registry.npmjs.org/@dxos%2fplugin-tictactoe
  Error verifying sigstore provenance bundle: Failed to validate repository
  information: package.json: "repository.url" is "", expected to match
  "https://github.com/dxos/plugins" from provenance

Provenance is on, and npm validates the signed statement against the published
`repository.url`, which the plugin never declared. The rejection lands after the
signature has been written to the transparency log, so nothing warns first — the
release is simply lost.

`check-packages-published` now fails on a publishable plugin whose
`repository.url` is absent or does not match `GITHUB_REPOSITORY`, alongside the
existing never-published check, so the next plugin cannot repeat this.

No changeset: 0.10.1 is bumped on `main` but was never published, so landing
this lets the release retry and ship a correct 0.10.1.

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

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

🦋 No changeset found

This PR changes plugin source but has no .changeset/*.md.
Affected: @dxos/plugin-tictactoe

  • If the change is consumer-relevant (worth a changelog entry), add a .changeset/<slug>.md.
  • If it isn't changelog-relevant (chore / refactor / internal), you can ignore this — the code still ships with the next release.

The registry step was gated on `steps.changesets.outputs.published` and fed by
`publishedPackages`, both of which exist only in the run that published to npm.
npm is append-only, so after it accepts a version a re-run finds nothing to
publish and skips the registry step entirely — the half that talks to two
external services was the half with no way to retry, short of a version bump.

Dispatching with `registry_only` now republishes the current versions to the
registry and skips npm.

Extracts the selection out of an inline `node -e` into `scripts/release-dirs.mjs`
so both modes are testable: `ALL=true` takes every publishable plugin, otherwise
`PUBLISHED` names the set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZ4ZdaPo9erkh5yF4eXh3Z
@wittjosiah wittjosiah changed the title fix(tictactoe): declare repository.url so provenance validates fix: unblock the release and make the registry publish retryable Aug 8, 2026
claude added 2 commits August 8, 2026 16:12
Both release scripts scanned `packages/*` and re-read each package.json to find
the name and `private` flag — re-deriving what pnpm already knows from the
workspace glob, and silently going wrong the day that glob changes.
`pnpm list --recursive --depth=-1 --json` reports name, version, path and
`private` directly, and drops the workspace root along with everything else not
meant for npm.

`repository` still comes from the manifest: pnpm does not report it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZ4ZdaPo9erkh5yF4eXh3Z
`pnpm list --recursive --depth=-1 --json` already reports name, version, path and
`private`, so the selection is a jq filter over it and does not need a script of
its own. Drops release-dirs.mjs and the shared workspace helper;
check-packages-published calls pnpm directly for the one field pnpm does not
report, `repository`.

Matches package names exactly. jq's `inside` compares strings by substring, so
releasing a plugin whose name merely contains another's would have republished
the other one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZ4ZdaPo9erkh5yF4eXh3Z
@wittjosiah
wittjosiah marked this pull request as ready for review August 8, 2026 16:33
@wittjosiah
wittjosiah added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 94eb3d0 Aug 8, 2026
3 checks passed
@wittjosiah
wittjosiah deleted the claude/plugin-excalidraw-pr8-cleanup-hb0ebu branch August 8, 2026 16:35
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.

2 participants