Testing Update Paths and Plugin Validation in OpenClaw
This page covers how OpenClaw validates update paths, package migrations, and plugin install/update behavior. It is essential for developers and QA engineers verifying package integrity and plugin lifecycle.
Read this when
- Changing OpenClaw update, doctor, package acceptance, or plugin install behavior
- Preparing or approving a release candidate
- Debugging package update, plugin dependency cleanup, or plugin install regressions
Checklist for update and plugin validation: confirm the installable package can modify real user state, repair outdated legacy state using doctor, and also install, load, update, and uninstall plugins from every supported origin.
For the complete test runner reference, see Testing. For live provider credentials and network-dependent test suites, see Testing live.
What we protect
- A package tarball must be complete, include a valid
dist/postinstall-inventory.json, and avoid relying on unpacked repository files. - Moving from an older published release to the candidate package must preserve config, agents, sessions, workspaces, plugin allowlists, and channel configuration.
openclaw doctor --fix --non-interactiveowns legacy cleanup and repair logic. Startup should not introduce hidden compatibility migrations for stale plugin state.- Plugin installation must work from local directories, git repositories, npm packages, and the ClawHub registry path.
- Plugin npm dependencies install in one managed npm project per plugin, get scanned before trust, and get removed via
npm uninstallduring plugin uninstall so hoisted dependencies do not remain. - Plugin update is a no-op when nothing changed: install records, resolved source, installed dependency layout, and enabled state stay intact.
Local proof during development
Start with a narrow scope:
pnpm changed:lanes --json
pnpm check:changed
pnpm test:changed
For plugin install, uninstall, dependency, or package-inventory modifications, also execute the targeted tests that cover the changed seam:
pnpm test src/plugins/uninstall.test.ts src/infra/package-dist-inventory.test.ts test/scripts/package-acceptance-workflow.test.ts
Before any package Docker lane consumes a tarball, verify the package artifact:
pnpm release:check
release:check runs config/docs/API drift checks (config schema, config docs baseline, plugin SDK API contract manifest and exports, plugin versions/inventory), writes the package dist inventory, runs npm pack --dry-run, rejects forbidden packed files, installs the tarball into a temp prefix, runs postinstall, and smokes bundled channel entrypoints.
Docker lanes
The Docker lanes serve as the product-level validation. They install or update a real package inside Linux containers and verify behavior through CLI commands, Gateway startup, HTTP probes, RPC status, and filesystem state.
Use focused lanes during iteration:
pnpm test:docker:plugins
pnpm test:docker:plugin-lifecycle-matrix
pnpm test:docker:plugin-update
pnpm test:docker:upgrade-survivor
pnpm test:docker:published-upgrade-survivor
pnpm test:docker:update-restart-auth
pnpm test:docker:update-migration
Key lanes:
test:docker:pluginscovers plugin install smoke, local folder installs, local folder update skip behavior, local folders with preinstalled dependencies,file:package installs, git installs with CLI execution, git moving-ref updates, npm registry installs with hoisted transitive dependencies, npm update no-ops, malformed npm package metadata rejection, local ClawHub fixture installs and update no-ops, marketplace update behavior, and Claude-bundle enable/inspect. SetOPENCLAW_PLUGINS_E2E_CLAWHUB=0to keep the ClawHub block hermetic/offline.test:docker:plugin-lifecycle-matrixinstalls the candidate package in a bare container, runs an npm plugin through install, inspect, disable, enable, explicit upgrade, explicit downgrade, and uninstall after deleting the plugin code. It logs RSS and CPU metrics per phase.test:docker:plugin-updateverifies that an unchanged installed plugin does not reinstall or lose install metadata duringopenclaw plugins update.test:docker:upgrade-survivorinstalls the candidate tarball over a dirty old-user fixture, runs package update plus non-interactive doctor, then starts a loopback Gateway and checks state preservation.test:docker:published-upgrade-survivorfirst installs a published baseline, configures it through a bakedopenclaw config setrecipe, updates it to the candidate tarball, runs doctor, checks legacy cleanup, starts the Gateway, and probes/healthz,/readyz, and RPC status.test:docker:update-restart-authinstalls the candidate package, starts a managed token-auth Gateway, unsets caller gateway auth env foropenclaw update --yes --json, and requires the candidate update command to restart the Gateway before the normal probes.test:docker:update-migrationis the cleanup-heavy published-update lane. It starts from a configured Discord/Telegram-style user state, runs baseline doctor so configured plugin dependencies have a chance to materialize, seeds legacy plugin dependency debris for a configured packaged plugin, updates to the candidate tarball, and requires post-update doctor to remove the legacy dependency roots.
Useful published-upgrade survivor variants:
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=openclaw@2026.4.23 \
OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=versioned-runtime-deps \
pnpm test:docker:published-upgrade-survivor
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=openclaw@latest \
OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=bootstrap-persona \
pnpm test:docker:published-upgrade-survivor
Available scenarios: base, acpx-openclaw-tools-bridge, feishu-channel, bootstrap-persona, channel-post-core-restore, plugin-deps-cleanup, configured-plugin-installs, stale-source-plugin-shadow, tilde-log-path, and versioned-runtime-deps. In aggregate runs, OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues (alias far-reaching) expands to all scenarios, including the configured-plugin install migration.
Full update migration is intentionally separate from Full Release CI. Use the manual Update Migration workflow when the release question is "can every published stable release from 2026.4.23 onward update to this candidate and clean up plugin dependency debris?":
gh workflow run update-migration.yml \
--ref main \
-f workflow_ref=main \
-f package_ref=main \
-f baselines=all-since-2026.4.23 \
-f scenarios=plugin-deps-cleanup
Package Acceptance
Package Acceptance is the GitHub-native package gate. It resolves one candidate package into a package-under-test tarball, records version and SHA-256, then runs reusable Docker E2E lanes against that exact tarball. The workflow harness ref is separate from the package source ref, so current test logic can validate older trusted releases.
Candidate sources:
source=npm: validateopenclaw@extended-stable,openclaw@beta,openclaw@latest, or an exact published version.source=ref: pack a trusted branch, tag, or commit with the selected current harness.source=url: validate a public HTTPS tarball with requiredpackage_sha256. This path rejects URL credentials, non-default HTTPS ports, private/internal hostnames or DNS/IP results, special-use IP space, and unsafe redirects.source=trusted-url: validate an HTTPS tarball with requiredpackage_sha256andtrusted_source_idagainst the maintainer-owned policy in.github/package-trusted-sources.json. Use this for enterprise/private mirrors instead of weakeningsource=urlwith an input-level allow-private switch. Bearer auth, when configured by policy, uses the fixedOPENCLAW_TRUSTED_PACKAGE_TOKENsecret.source=artifact: reuse a tarball uploaded by another Actions run.
Full Release Validation defaults to source=artifact, which is constructed from the resolved release SHA. To verify after publication, supply package_acceptance_package_spec=openclaw@YYYY.M.PATCH so the same upgrade matrix targets the published npm package rather than the built artifact.
Release checks feed Package Acceptance the package, update, restart, and plugin set:
doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update plugin-binding-command-escape
When release soak is enabled (mandatory for release_profile=stable and full), these are also passed:
published_upgrade_survivor_baselines=last-stable-4 2026.4.23 2026.5.2 2026.4.15
published_upgrade_survivor_scenarios=reported-issues
telegram_mode=mock-openai
This ensures package migration, update channel switching, tolerance for corrupt managed plugins, stale plugin dependency cleanup, offline plugin coverage, plugin update behavior, and Telegram package QA all use the same resolved artifact without requiring the default release package gate to walk every published release.
last-stable-4 resolves to the four most recent stable OpenClaw releases published to npm. Release package acceptance pins 2026.4.23 as the first plugin update compatibility boundary, 2026.5.2 as a plugin architecture churn boundary, and 2026.4.15 as an older 2026.4.1x published update baseline. The resolver deduplicates any pins already among the latest four. For thorough published update migration coverage, use all-since-2026.4.23 in the separate Update Migration workflow instead of Full Release CI. release-history stays available for manual broader sampling when you also need the legacy pre date anchor.
When multiple published upgrade survivor baselines are selected, the reusable Docker workflow splits each baseline into its own dedicated runner job. Each baseline shard still runs the chosen scenario set, but logs and artifacts are kept per baseline, and wall time is limited by the slowest shard instead of a single large serial job.
Run a package profile manually when validating a candidate before release:
gh workflow run package-acceptance.yml \
--ref main \
-f workflow_ref=main \
-f source=npm \
-f package_spec=openclaw@beta \
-f suite_profile=package \
-f published_upgrade_survivor_baselines="last-stable-4 2026.4.23 2026.5.2 2026.4.15" \
-f published_upgrade_survivor_scenarios=reported-issues \
-f telegram_mode=mock-openai
For a published extended stable canary, set package_spec=openclaw@extended-stable. Package Acceptance resolves that selector into a precise tarball before the Docker lanes run.
Use suite_profile=product when the release question includes MCP channels, cron/subagent cleanup, OpenAI web search, or OpenWebUI. Use suite_profile=full only when full Docker release path coverage is required.
Release default
For release candidates, the default proof stack is:
pnpm check:changedandpnpm test:changedfor source level regressions.pnpm release:checkfor package artifact integrity.- Package Acceptance
packageprofile or the release check custom package lanes for install, update, restart, and plugin contracts. - Cross OS release checks for OS specific installer, onboarding, and platform behavior.
- Live suites only when the changed surface touches provider or hosted service behavior.
On maintainer machines, broad gates and Docker/package product proof should run in Testbox unless local proof is explicitly needed.
Legacy compatibility
Compatibility leniency is narrow and time boxed:
- Packages through
2026.4.25, including2026.4.25-beta.*, may tolerate already shipped package metadata gaps in Package Acceptance. - The published
2026.4.26package may warn for local build metadata stamp files already shipped. - Later packages must satisfy modern contracts. The same gaps cause failure instead of warning or skipping.
Do not add new startup migrations for these old shapes. Add or extend a doctor repair, then prove it with upgrade-survivor, published-upgrade-survivor, or update-restart-auth when the update command owns the restart.
Adding coverage
When changing update or plugin behavior, add coverage at the lowest layer that can fail for the right reason:
- Pure path or metadata logic: unit test beside the source.
- Package inventory or packed file behavior:
package-dist-inventoryor tarball checker test. - CLI install/update behavior: Docker lane assertion or fixture.
- Published release migration behavior:
published-upgrade-survivorscenario. - Update owned restart behavior:
update-restart-auth. - Registry/package source behavior:
test:docker:pluginsfixture or ClawHub fixture server. - Dependency layout or cleanup behavior: assert both runtime execution and the filesystem boundary. npm dependencies may be hoisted inside the plugin's managed npm project, so tests should prove that project is scanned/cleaned instead of assuming only the plugin package local
node_modulestree.
Keep new Docker fixtures hermetic by default. Use local fixture registries and fake packages unless the point of the test is live registry behavior.
Failure triage
Start with the artifact identity:
- Package Acceptance
resolve_packagesummary: source, version, SHA 256, and artifact name. - Docker artifacts:
.artifacts/docker-tests/**/summary.json,failures.json, lane logs, and rerun commands. - Upgrade survivor summary:
.artifacts/upgrade-survivor/summary.json, including baseline version, candidate version, scenario, phase timings, and config recipe coverage.
Prefer rerunning the failed exact lane with the same package artifact over rerunning the whole release umbrella.