--- name: openclaw-testing description: Choose, run, rerun, or debug OpenClaw tests, CI checks, Docker E2E lanes, release validation, and the cheapest safe verification path. --- # OpenClaw Testing Use this skill when deciding what to test, debugging failures, rerunning CI, or validating a change without wasting hours. ## Read First - `docs/reference/test.md` for local test commands. - `docs/ci.md` for CI scope, release checks, Docker chunks, and runner behavior. - Scoped `AGENTS.md` files before editing code under a subtree. ## Default Rule Prove the touched surface first. Do not reflexively run the whole suite. Agent sessions are remote-first for tests and computationally intensive work. Classify source trust before selecting a backend. Trusted maintainer code defaults to Blacksmith Testbox. Untrusted contributor or fork code must use secretless fork CI or sanitized direct AWS Crabbox; never sync or run it on the credential-hydrated Blacksmith workflow. When trusted work is likely to change code or need tests, builds, typechecks, lint fan-out, Docker, packaging, E2E, or live proof, immediately start this in a background command session: ```bash node scripts/crabbox-wrapper.mjs warmup \ --provider blacksmith-testbox \ --keep \ --timing-json ``` For untrusted code, switch to a clean trusted `main` checkout and pre-warm direct AWS with an installed trusted Crabbox binary. Do not execute the untrusted checkout's wrapper or config locally: ```bash cd env -u CRABBOX_AWS_INSTANCE_PROFILE \ crabbox config show --json | \ jq -e '.aws.instanceProfile == ""' >/dev/null env -u CRABBOX_AWS_INSTANCE_PROFILE \ -u CRABBOX_TAILSCALE \ -u CRABBOX_TAILSCALE_AUTH_KEY \ -u CRABBOX_TAILSCALE_AUTH_KEY_ENV \ -u CRABBOX_TAILSCALE_EXIT_NODE \ -u CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS \ -u CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE \ -u CRABBOX_TAILSCALE_TAGS \ crabbox warmup \ --provider aws \ --network public \ --tailscale=false \ --tailscale-exit-node= \ --tailscale-exit-node-allow-lan-access=false \ --keep \ --timing-json crabbox inspect --provider aws --id --json | \ jq -e '.network == "public" and .tailscale == null' >/dev/null ``` Bind the returned lease to one immutable reviewed head SHA; never repurpose a trusted or previously hydrated lease, and stop/rewarm if the head changes. Record the reviewed PR's full head SHA with `gh pr view --repo --json headRefOid --jq .headRefOid`. Every untrusted AWS run must override the repo env allowlist, skip Actions hydration, and upload the trusted bootstrap script from clean `main` alongside `--fresh-pr`. The script bypasses raw-box JavaScript preflight, proves the identity boundary, installs pinned Node/pnpm, verifies the exact SHA and package-manager pin, isolates `HOME`, installs dependencies, then runs the requested test command: ```bash env -u CRABBOX_AWS_INSTANCE_PROFILE \ CRABBOX_ENV_ALLOW=CI \ crabbox run \ --provider aws \ --id \ --fresh-pr \ --no-hydrate \ --timing-json \ --script scripts/crabbox-untrusted-bootstrap.sh -- \ /usr/local/bin/pnpm test # After all proof: env -u CRABBOX_AWS_INSTANCE_PROFILE \ crabbox stop --provider aws ``` Continue inspection and editing while the remote box hydrates. Save the returned id, reuse it for the task's focused tests and heavy gates, sync the current checkout on every run, and stop it before handoff. Do not pre-warm for read-only, docs-only, or clearly trivial work that will not run tests or heavy commands. 1. Inspect the diff and classify the touched surface: - any agent-run test, focused or broad: run it on the pre-warmed safe remote backend; Blacksmith Testbox only for trusted maintainer code - changed gates, builds, typechecks, lint fan-out, Docker, package, E2E, or live work: run it remotely; these are never routine laptop work - normal source checkout, `pnpm check:changed`: it delegates to Crabbox/Testbox, but prefer the explicit kept-lease path when a Testbox was pre-warmed so the task reuses one lease - explicit local fallback requested by the user, one/few files: `node scripts/run-vitest.mjs ` - direct AWS Crabbox proof: pass `--provider aws`; untrusted code also requires the sanitized invocation above - workflow-only: `git diff --check`, workflow syntax/lint (`actionlint` when available) - docs-only: `pnpm docs:list`, docs formatter/lint only if docs tooling changed or requested 2. Reproduce narrowly before fixing. 3. Fix root cause. 4. Rerun the same narrow proof. 5. Broaden only when the touched contract demands it. ## Guardrails - Do not kill unrelated processes or tests. If something is running elsewhere, treat it as owned by the user or another agent. - Do not run tests or computationally intensive commands locally unless the user explicitly asks for local proof. Remote-provider unavailability permits only a narrow reported fallback, not a silent local full gate. - Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets. - Use `scripts/committer "" ` when committing; stage only your files. - If dependencies are missing on the selected remote box, run `pnpm install` there, retry once, then report the first actionable error. Do not reconcile or reinstall a local Codex worktree merely to run validation. - In a Codex worktree or linked/sparse checkout, do not run direct local `pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, or `scripts/committer`. Use `node scripts/crabbox-wrapper.mjs` for remote proof, and `git commit --no-verify` only after the relevant remote proof is already clean. The direct `node scripts/run-vitest.mjs` path is an explicit local fallback only. - For remote proof, use the Crabbox wrapper first, but name the actual backend. Direct AWS Crabbox uses `provider=aws` and `cbx_...` ids. Delegated Blacksmith Testbox through Crabbox uses `provider=blacksmith-testbox`, `syncDelegated=true`, and `tbx_...` ids. Both satisfy "remote proof" when the requested proof surface allows either. - Treat contributor and fork patches as untrusted unless a maintainer explicitly approves credentialed execution after review. For untrusted AWS runs, `CRABBOX_ENV_ALLOW=CI` must replace the repo's `OPENCLAW_*` allowlist, `--no-hydrate` must block auth-profile hydration, and the remote command must use a fresh temporary `HOME`. The lease must be newly warmed for and bound to one reviewed head SHA, never trusted or previously hydrated; stop and rewarm when the SHA changes. Do not execute repo scripts or config from the untrusted local checkout: launch an installed trusted Crabbox binary from a clean trusted `main` checkout and fetch the PR with `--fresh-pr`. Unset `CRABBOX_AWS_INSTANCE_PROFILE` and fail closed unless `crabbox config show --json` resolves an empty `aws.instanceProfile`. Before any install/test, use trusted absolute-path tools to require an IMDSv2 token, prove the IAM credentials endpoint returns 404, and compare remote `git rev-parse HEAD` with the full reviewed head SHA. Unset all `CRABBOX_TAILSCALE*` overrides, pass `--network public --tailscale=false`, clear exit-node/LAN flags, then require `crabbox inspect` to report `network=public` and no Tailscale state before uploading any script. Upload trusted `scripts/crabbox-untrusted-bootstrap.sh` with `--fresh-pr`; it bootstraps Node 24 and repository-pinned pnpm before executing PR code and rejects a changed `packageManager` pin before install. If the broker cannot provide that no-role proof or no remote PR exists, use secretless fork CI. Do not select `hydrate-github` or a credential-hydrated Testbox workflow. - Do not infer "no Testbox is running" from plain `blacksmith testbox list`. Use `blacksmith testbox list --all` or `blacksmith testbox status ` before reporting cloud state. - Reuse only an id/slug created in this operator session unless explicitly coordinating with another lane. If Testbox queues, fails capacity, or cannot allocate, report the blocker or switch to direct AWS Crabbox only when that still proves the requested surface. - Reuse does not mean stale source: omit `--no-sync` so every run uploads the current checkout. Use `--no-sync` only to rerun an unchanged, already-synced tree intentionally. ## Explicit Local Test Fallbacks These commands are for human workflows or an agent's explicit local fallback. They are not the default agent path. ```bash pnpm changed:lanes --json pnpm check:changed # Crabbox/Testbox changed typecheck/lint/guards; no Vitest pnpm test:changed # cheap smart changed Vitest targets pnpm verify # full check, then full Vitest OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed pnpm test -- --reporter=verbose OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test ``` Use targeted file paths whenever possible. Avoid raw `vitest`; use the repo `pnpm test` wrapper so project routing, workers, and setup stay correct. If raw Vitest is unavoidable, use `vitest run ...`; bare `vitest ...` starts local watch mode and will not exit on its own. When the checkout is a Codex worktree, prefer the direct node harness instead: ```bash node scripts/run-vitest.mjs ``` That keeps the test scoped without giving pnpm a chance to run dependency status checks or install reconciliation in a linked worktree. ## Plugin Package And Live Proof When validating an external or official plugin package, prove the package shape and trust shape separately. Do not use raw archive/path installs to prove the managed dependency path, and do not treat `npm-pack:` as proof of catalog-linked official trust. - For local release-candidate proof, pack the plugin and install it with `openclaw plugins install npm-pack: --force`. This uses the managed per-plugin npm project and is the closest local substitute for the registry artifact's dependency behavior. - If the behavior depends on bundled-plugin or trusted official plugin status, add a second proof through a catalog-backed official install or a published package path that records official trust. Local `npm-pack:` proof alone is not sufficient for privileged helpers or trusted-official scope handling. - Treat missing runtime imports as package-manifest bugs first. Runtime code must depend on packages declared in the plugin package `dependencies` or `optionalDependencies`; do not make a final proof depend on manually running `npm install` inside `~/.openclaw/npm/projects/...`. - If the plugin ships `npm-shrinkwrap.json`, regenerate or check it after moving dependencies between dev and runtime sections. - Inspect the packed tarball when dependency ownership or generated `dist/` matters: verify `package/package.json`, the expected runtime files, and any package-local shrinkwrap before installing it on a live host. - After installing the package, restart the Gateway when the touched surface is plugin registration, runtime dependency loading, privileged helpers, provider routing, or generated dist. - For live provider or channel probes, add only temporary config needed for the proof, then remove it and verify the cleanup state before closeout. ## Command Semantics - `pnpm check` and `pnpm check:changed` do not run Vitest tests. They are for typecheck, lint, and guard proof. - `pnpm test` and `pnpm test:changed` run Vitest tests. - `pnpm verify` runs `pnpm check`, then `pnpm test`, with Crabbox phase markers so remote summaries show which half failed. - `pnpm test:changed` is intentionally cheap by default: direct test edits, sibling tests, explicit source mappings, and import-graph dependents. - `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` is the explicit broad fallback for harness/config/package edits that genuinely need it. - Do not run extension sweeps just because core changed. If a core edit is for a specific plugin bug, run that plugin's tests explicitly. If a public SDK or contract change needs consumer proof, choose the smallest representative plugin/contract tests first, then broaden only when the risk justifies it. - The test wrapper prints a short `[test] passed|failed|skipped ... in ...` line. Vitest's own duration is still the per-shard detail. ## Routing Model - `pnpm changed:lanes --json` answers "which check lanes does this diff touch?" It is used by `pnpm check:changed` for typecheck/lint/guard selection. - `pnpm test:changed` answers "which Vitest targets are worth running now?" It uses the same changed path list, but applies a cheaper test-target resolver. - Direct test edits run themselves. Source edits prefer explicit mappings, sibling `*.test.ts`, then import-graph dependents. Shared harness/config/root edits are skipped by default unless they have precise mapped tests. - Shared group-room delivery config and source-reply prompt edits are precise mapped tests: they run the core auto-reply regressions plus Discord and Slack delivery tests so cross-channel default changes fail before a PR push. - Public SDK or contract edits do not automatically run every plugin test. `check:changed` proves extension type contracts; the agent chooses the smallest plugin/contract Vitest proof that matches the actual risk. - Use `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` only when a harness, config, package, or unknown-root edit really needs the broad Vitest fallback. ## CI Debugging Start with current run state, not logs for everything: ```bash gh run list --branch main --limit 10 gh run view --json status,conclusion,headSha,url,jobs gh run view --job --log ``` - Check exact SHA. Ignore newer unrelated `main` unless asked. - For cancelled same-branch runs, confirm whether a newer run superseded it. - Fetch full logs only for failed or relevant jobs. - Prefer `gh run view --json jobs` over PR rollup while debugging; rollup can be stale/noisy. - For `prompt:snapshots:check` failures, treat Linux Node 24 as CI truth. If macOS passes but CI drifts, reproduce in a Linux Node 24 container or Testbox, commit that generated output, then rerun. ## GitHub Release Workflows Use the smallest workflow that proves the current risk. The full umbrella is available, but it is usually the last step after narrower proof, not the first rerun after a focused patch. ### Full Release Validation `Full Release Validation` (`.github/workflows/full-release-validation.yml`) is the manual "everything before release" umbrella. It resolves a target ref, then dispatches: - manual `CI` for the full normal CI graph, with Android enabled via `include_android=true` - `Plugin Prerelease` for release-only plugin static checks, extension shards, the release-only `agentic-plugins` shard, and plugin product Docker lanes - `OpenClaw Release Checks` for install smoke, cross-OS release checks, live and E2E checks, Docker release-path suites, OpenWebUI, QA Lab, fast Matrix, and Telegram release lanes - optional post-publish Telegram E2E when a package spec is supplied Run it only when validating an actual release candidate, after broad shared CI or release orchestration changes, or when explicitly asked: ```bash gh workflow run full-release-validation.yml \ --repo openclaw/openclaw \ --ref main \ -f ref= \ -f provider=openai \ -f mode=both \ -f release_profile=stable ``` Run the workflow itself from the trusted current ref, normally `--ref main`; child workflows are dispatched from that same ref even when `ref` points at an older release branch or tag. Full Release Validation has no separate child workflow ref input; choose the trusted harness by choosing the workflow run ref. Use `release_profile=minimum|stable|full` to control live/provider breadth: `minimum` keeps the fastest OpenAI/core release-critical set, `stable` adds the stable provider/backend set, and `full` adds the broad advisory provider/media matrix. Do not make `full` faster by silently dropping suites; optimize setup, artifact reuse, and sharding instead. The parent verifier job appends a child overview plus slowest-job tables for child runs; rerun only that verifier after a child rerun turns green. Standalone manual `CI` dispatches do not run the plugin prerelease suite, the extension batch sweep, or the release-only `agentic-plugins` Vitest shard. Those lanes are intentionally reserved for the separate `Plugin Prerelease` child so PRs, main pushes, and ad hoc broad CI checks do not spend Docker/package time or all-plugin runtime time on release-only product coverage. If a full run is already active on a newer `origin/main`, prefer watching that run over dispatching a duplicate. Do not cancel release, release-check, or child workflow runs unless Peter explicitly asks for cancellation. The child-dispatch jobs record the child run ids. The final `Verify full validation` job re-queries those child runs and is the canonical parent gate. If a child workflow failed but was later rerun successfully, rerun only the failed parent verifier job; do not dispatch a new full umbrella unless the release evidence is stale. For bounded recovery after a focused fix, pass `-f rerun_group=`. Supported umbrella groups are `all`, `ci`, `plugin-prerelease`, `release-checks`, `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, and `npm-telegram`. Use the narrowest group that covers the failed box. After a targeted release-check fix, do not restart the full umbrella by habit: dispatch the matching `rerun_group` and rerun only the parent verifier/evidence step after the child is green unless the release evidence is stale. For a single failed live/E2E shard, use `-f rerun_group=live-e2e -f live_suite_filter=` so the Blacksmith workflow only spends setup and queue time on that suite. ### Release Evidence After release-candidate validation or before a release decision, record the important run ids in the public `openclaw/releases` evidence ledger. Use the manual `OpenClaw Release Evidence` (`openclaw-release-evidence.yml`) workflow there. It writes durable summaries under `evidence//` and commits: - `release-evidence.md` - `release-evidence.json` - `index.json` - `runs/