Release Checklist (npm + macOS)
Usepnpm (Node 24+) from the repo root. Keep the working tree clean before tagging/publishing.
Operator trigger
When the operator says “release”, immediately do this preflight (no extra questions unless blocked):- Read this doc and macOS release.
- Confirm
SPARKLE_PRIVATE_KEY_FILEand App Store Connect vars are set in the operator environment. - Keep Sparkle private keys outside the repo. Use the operator backup location configured for this host.
- Version & metadata
package.json is the single source of truth and release-please
owns the bump. Derived version files are synchronized by pnpm version:sync in CI and by the
release workflow.
- Confirm the release-please PR contains the intended
package.jsonversion bump. - Confirm the release-please sync commit ran
pnpm version:syncand updated derived version files. - Confirm extension package versions match the root version when they are part of the workspace release.
- Do not edit
docs/install/updating.mdfor a pinned npm version; it intentionally installswednesdayai@latest. - Peekaboo and Swabble version independently; bump them only when that specific release requires it.
appcast.xml version here — that is updated in step 5 (macOS Sparkle release).
- Confirm package metadata: root package name is
wednesdayai(currently0.4.11),binexposes bothwednesdayaiand the compatibilityopenclawcommand, and WhatsApp package name is@wednesdayai/whatsapp. - Publication runs only from
.github/workflows/npm-publish.ymlusing thenpm-publishGitHub environment (OIDC trusted publishing; npm staged publishes for stable). There is no second publish path. - If dependencies changed, run
pnpm installsopnpm-lock.yamlis current.
- Build & artifacts
- If A2UI inputs changed, run
pnpm canvas:a2ui:bundleand commit any updatedsrc/canvas-host/a2ui/a2ui.bundle.js. -
pnpm run build(regeneratesdist/). - Verify npm package
filesincludes all requireddist/*folders (notablydist/node-host/**anddist/acp/**for headless node + ACP CLI). - Confirm the packed tarball ships no
docs/content outsidedocs/reference/templates/(full user/admin/developer docs live atdocs.wednesdayai.devand on GitHub, not in the install). When validating sealed package inventories, pass both--sealed-inventoryinputs topnpm release:check;scripts/release-check.tsthen rejects any otherdocs/path and requiresdocs/reference/templates/AGENTS.mdandBOOTSTRAP.md. A plainpnpm release:checkdoes not inspect a packed tarball. - Confirm
dist/build-info.jsonexists and includes the expectedcommithash (CLI banner uses this for npm installs). - Optional:
npm pack --pack-destination /tmpafter the build; inspect the tarball contents and keep it handy for the GitHub release (do not commit it).
- Changelog & docs
- Do not hand-edit root
CHANGELOG.md; release-please generates it from Conventional Commits. - Add or update a dated
dev-docs/logs/YYYY-MM-DD-*.mdentry for the engineering record when the release contains feature work. - Update
dev-docs/CHANGELOG.mdonly as the narrative index to that dev log. - Ensure README examples/flags match current CLI behavior (notably new commands or options).
- Validation
-
pnpm build -
pnpm check -
pnpm test(orpnpm test:coverageif you need coverage output) -
pnpm release:check(static only: package identity, plugin versions, Sparkle floors, plugin-sdk exports. It does not runnpm pack. Use two--sealed-inventory <path>arguments only when validating a sealed candidate.) -
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke(Docker install smoke test, fast path; required before release)- If the immediate previous npm release is known broken, set
OPENCLAW_INSTALL_SMOKE_PREVIOUS=<last-good-version>orOPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS=1for the preinstall step.
- If the immediate previous npm release is known broken, set
- (Optional) Full installer smoke (adds non-root + CLI coverage):
pnpm test:install:smoke - (Optional) Installer E2E (Docker, runs the WednesdayAI install script, onboards, then runs real tool calls):
pnpm test:install:e2e:openai(requiresOPENAI_API_KEY)pnpm test:install:e2e:anthropic(requiresANTHROPIC_API_KEY)pnpm test:install:e2e(requires both keys; runs both providers)
- (Optional) Spot-check the web gateway if your changes affect send/receive paths.
- macOS app (Sparkle)
- Build + sign the macOS app, then zip it for distribution.
- Generate the Sparkle appcast (HTML notes via
scripts/make_appcast.sh) and updateappcast.xml. - Keep the app zip (and optional dSYM zip) ready to attach to the GitHub release.
- Follow macOS release for the exact commands and required env vars.
APP_BUILDmust be numeric + monotonic (no-beta) so Sparkle compares versions correctly.- If notarizing, use the
openclaw-notarykeychain profile created from App Store Connect API env vars (see macOS release).
- Publish (npm)
.github/workflows/npm-publish.yml (trusted publishing through the npm-publish GitHub environment; npm ≥ 11.15.0 staged publishing for stable).
- On release creation, the
dispatch-stable-stagejob inrelease-please.ymlautomatically dispatches npm-publish stable staging withrelease_tagandsource_sharesolved from the final tag SHA (release-please may heal derived versions and move the tag before this runs). No human dispatch is needed for the normal flow. - Run
npm stage listto find the stage IDs for the release versions of@wednesdayai/whatsappandwednesdayai. Approve only packages for which staging created a stage, each separately withnpm stage approve <stage-id>(2FA required). Skip approval for a package already live. This approval gate is intentionally human. - After all created stages are approved, dispatch verification:
gh workflow run npm-publish.yml --ref main -f release_tag=vX.Y.Z -f source_sha=<40-hex tag sha> -f verify_approved=yes. Verification checks both packages, including any skipped package, against the recorded pack integrities and installed pair. - Verify the registry:
npm view wednesdayai version,npm view wednesdayai dist-tags,npm view @wednesdayai/whatsapp version,npm view @wednesdayai/whatsapp dist-tags, andnpx -y wednesdayai@X.Y.Z --version(or--help). - The scheduled
npm-paritywatchdog backstops this list: it opens or updates a tracking issue when a release tag has no registry counterpart or the dev rail stalls. Treat its issue as release debt, not noise.
@wednesdayai/whatsapp and then wednesdayai with npm stage publish in the same stable job (a package already live at that version is skipped), then stops for maintainer approval of any created stages. Dev publication publishes WhatsApp, then core, directly to the dev dist-tag. Registry mutations name an exact sealed tarball — automation never publishes a working directory and never administers stages or dist-tags through OIDC.
Missed release (tag exists, registry does not)
If a release tag was created but the stable publish never landed (for examplev0.4.11 after the old token-auth EOTP failure), retro-publish from the tag once the identity preconditions hold (source_sha is the full tag commit SHA, the GitHub Release targetCommitish equals that SHA, both package.json and extensions/whatsapp/package.json at that commit have the version in the tag name, and the tag is an ancestor of main):
Troubleshooting
- npm pack/publish hangs or produces huge tarball: the macOS app bundle in
dist/OpenClaw.app(and release zips) get swept into the package. Fix by whitelisting publish contents viapackage.jsonfiles(include dist subdirs,docs/reference/templates, skills; exclude app bundles and any otherdocs/path). Confirm withnpm pack --dry-runthatdist/OpenClaw.appis not listed. - npm auth web loop for dist-tags: use legacy auth to get an OTP prompt:
NPM_CONFIG_AUTH_TYPE=legacy npm dist-tag add wednesdayai@X.Y.Z latestNPM_CONFIG_AUTH_TYPE=legacy npm dist-tag add @wednesdayai/whatsapp@X.Y.Z latest
npxverification fails withECOMPROMISED: Lock compromised: retry with a fresh cache:NPM_CONFIG_CACHE=/tmp/npm-cache-$(date +%s) npx -y wednesdayai@X.Y.Z --version
- Tag must not be moved after a late fix: release tags are immutable. Cut a new patch version instead of running
git tag -f.
- GitHub release + appcast
- Release-please owns tag creation and push for normal releases. Manual
git tag vX.Y.Z && git push origin vX.Y.Z(orgit push --tags) is only for exceptional flows — release tags are immutable once published (see Troubleshooting). - Create/refresh the GitHub release for
vX.Y.Zwith titleopenclaw X.Y.Z(not just the tag); body should include the full changelog section for that version (Highlights + Changes + Fixes), inline (no bare links), and must not repeat the title inside the body. - Attach artifacts:
npm packtarball (optional),OpenClaw-X.Y.Z.zip, andOpenClaw-X.Y.Z.dSYM.zip(if generated). - Commit the updated
appcast.xmland push it (Sparkle feeds from main). - From a clean temp directory (no
package.json), runnpx -y wednesdayai@X.Y.Z send --helpto confirm install/CLI entrypoints work. - Announce/share release notes.
Trusted npm publishing
Publication runs only from.github/workflows/npm-publish.yml through the npm-publish GitHub environment (OIDC trusted publishing; npm staged publishes for stable). Step 6 above is the operator flow. For the architecture and the vendoring recipe, see Trusted npm publishing for contributors.
Version locations
package.json version is the single source of truth. Only the release-please Release PR bumps it; never hand-edit it (CI version:guard rejects hand edits). All other locations are derived by pnpm version:sync; never edit them directly:
apps/android/app/build.gradle.kts(versionNameandversionCode)apps/ios/project.yml(the xcodegen source of truth for the app plus the Share and Watch extensions) and the generatedapps/ios/**/Info.plistfiles (Sources, Tests, ShareExtension, WatchApp, WatchExtension), allCFBundleShortVersionStringandCFBundleVersion. Async-versionsmeta-test fails if any version-bearing iOS file is left unstamped.apps/macos/Sources/OpenClaw/Resources/Info.plist(CFBundleShortVersionStringandCFBundleVersion)packages/clawdbot/package.jsonandpackages/moltbot/package.json(shimversion)- every
extensions/*/package.jsonversionfield
- Mobile build numbers derive deterministically from the SemVer components (
major*10000+minor*100+patch), not from the commit count. docs/install/updating.mdis not derived. It installs@latestand pins no version.- Peekaboo and Swabble are not auto-stamped. They version independently of the npm package; bump them by hand if a release requires it.
- Do not touch
appcast.xmlunless cutting a new macOS Sparkle release.
Changelog ownership
The releaseCHANGELOG.md is generated by release-please from Conventional Commits. Do not hand-edit it (ADR 0005). Record narrative history in a dated dev log under dev-docs/logs/ and add a pointer to dev-docs/CHANGELOG.md, the narrative index. Build provenance (version, channel, commit, fork base) lives in dist/build-info.json; the bare wednesdayai --version stays a single SemVer line.
Deprecation policy
Config keys, CLI flags, and SDK exports are never removed immediately when renamed or replaced. Ship a deprecated alias for at least one release cycle, then remove it in a later release with an explicit announcement.- A rename plus alias is backward-compatible, so use a
feat:orfix:commit, notBREAKING CHANGE. - Reserve
BREAKING CHANGEor!for hard removals and incompatibilities with no migration path. - The
openclawtowednesdayairename is the canonical example. The old name remains a permanent alias, so that refactor was never a breaking change.
Plugin publish scope (npm)
WednesdayAI extensions use the@wednesdayai npm organization for new first-party plugin
packages:
@wednesdayai/*— WednesdayAI-native extensions built for this fork (new additions go here).@openclaw/*— inherited extensions from the fork base. Publish only if the package is already on npm under that scope; do not add new extensions under@openclaw/— use@wednesdayai/instead.
extensions/**).
@wednesdayai/whatsapp is the first dependency-bearing first-party plugin on the npm rail.
Process to derive future publish candidates:
- Start from plugin ids under
extensions/*. - Compare with
extensions/*/package.jsonnames. - Publish new WednesdayAI-native packages under
@wednesdayai/<plugin-id>only after the package has built JavaScript entries and runtime dependency validation. - Keep inherited
@openclaw/*packages compatibility-only unless a package is known to target this fork.
tlon).