- Full testing kit (suites, live, Docker): Testing
- Update and plugin package validation: Testing updates and plugins
Agent default
Agent sessions run trusted development tests, changed gates, typecheck/lint, and builds locally by default, broadening only when the touched contract requires it. Never execute untrusted repository tooling locally. Use Crabbox when the environment is part of the proof: clean-machine, install/package, Docker, E2E, live, desktop, or cross-platform work, or when the operator explicitly requests remote proof. Do not use Crabbox merely as generic compute offload. The configured Testbox workflow hydrates credentials, so untrusted contributor or fork code must use secretless fork CI or sanitized direct AWS Crabbox instead. Do not pre-warm for anticipated work. Acquire the backend lazily when the first environment-sensitive command is ready, reuse the returnedtbx_... id
for later remote commands, sync the current checkout on every run, and stop it
before handoff.
After the first successful reuse, the wrapper records the lease’s base,
dependency, and Testbox workflow fingerprint under .crabbox/testbox-leases/.
Source-only edits keep reusing the warmed box. A changed merge base, lockfile,
package-manager input, wrapper, or Testbox workflow fails closed and requires a
fresh lease. Every run still syncs the current checkout.
OPENCLAW_TESTBOX_ALLOW_STALE=1 is only for intentional diagnostics, not
release proof.
The Testbox workflow registers a separate disposable checkout for native sync.
The hydrated execution workspace stays at its original absolute path, so native
Git cleanup and rsync cannot delete dependencies, build output, or ignored runtime
there. The wrapper verifies and applies the source bundle in that execution
workspace, then runs the payload there. It never restores runtime from the caller
or changes the selected rsync binary.
Workspace preparation changes require a fresh lease. A missing or overlapping
execution-workspace binding stops the payload; stop that lease and warm a new one.
Use the OpenClaw wrapper for proof: direct native Blacksmith commands target the
transport checkout, which deliberately has no hydrated runtime.
Testbox requests with --artifact-glob or --require-artifact also require the
prepared-artifact-workspace feature in the selected Crabbox binary’s
providers describe blacksmith-testbox --json response. The wrapper checks this
before sync or lease work, rather than collecting missing or stale transport files.
Update Crabbox if that capability is absent. Collection stays anchored in the
prepared workspace across payload directory changes and normal failure exits;
existing cancellation and signal-related artifact withholding remains unchanged.
Ordinary runs without artifact requests do not require this additional capability.
Testbox runs and POSIX remote changed gates freeze source into a Git bundle
against the pinned base. These runs require Crabbox 0.37.0 or later for
sync-plan --json; upgrade Crabbox before retrying an older binary. This API floor
does not apply to help, warmup, status, or runs that do not prepare a source bundle.
Selection uses Crabbox’s sync policy and Git’s repository, info, and effective global
exclusions, including repo-local overrides, for untracked files. Tracked ignored
files and staged ignored additions remain source; an explicit privacy exclusion
conflicting with required tracked source stops the run before upload.
The command binds the bundle digest and raw source tree. Before running the payload,
the receiver applies deletions and restores file bytes, symlink target bytes, and
Git executable modes, then verifies the filesystem directly. Git text filters do not
normalize this snapshot. Missing, stale, or mismatched bundles fail closed.
Producer-declared deletions must also be absent, even when the remote index has lost
them. Deletions use the same privacy policy as source selection; unknown ignored
runtime data is preserved. Unexpected nonignored receiver files stop the run instead
of being deleted. The verification receipt reports the original source revision
separately from the synthetic transport commit; remote HEAD identifies that
verified transport tree, and changed gates compare it with the pinned base.
Raw-byte differences can conservatively select additional changed paths. Git path
names must be UTF-8; symlink targets remain raw bytes. Symlinked repository Crabbox
configuration or ignore files, and privacy-excluded runtime configuration, are
rejected before upload rather than changing their trust or privacy treatment.
Local test commands below are the normal trusted development path. Keep proof
proportional to the touched contract.
For untrusted proof, lazily warm with --provider aws. Every run must set
CRABBOX_ENV_ALLOW=CI, pass --provider aws --no-hydrate, and use
a fresh temporary remote HOME before installing dependencies or running
tests. Use a newly warmed lease dedicated to that untrusted source; never reuse
a trusted or previously hydrated lease. Launch an installed trusted Crabbox
binary from a clean trusted main checkout and fetch only the remote PR with
--fresh-pr; never execute the untrusted checkout’s wrapper or config locally.
Unset CRABBOX_AWS_INSTANCE_PROFILE and fail closed unless resolved
aws.instanceProfile is empty. Before any install/test, use trusted
absolute-path tools to require an IMDSv2 token, prove the IAM credentials
endpoint returns 404, and verify remote git rev-parse HEAD equals the full
reviewed PR head SHA. Bind the lease to that SHA and stop/rewarm when the head
changes. Upload trusted scripts/crabbox-untrusted-bootstrap.sh from clean
main alongside --fresh-pr; it installs pinned Node/pnpm, verifies the SHA
and package-manager pin, isolates HOME, installs dependencies, then executes
the requested test. If the broker cannot prove no role or no remote PR exists,
use secretless fork CI. Do not use hydrate-github, --no-sync, or a
credential-hydrated Testbox workflow.
Unset all CRABBOX_TAILSCALE* overrides, force --network public --tailscale=false, clear exit-node/LAN flags, and require crabbox inspect to
report public networking with no Tailscale state before uploading any script.
Crabbox repository setup
The shared Crabbox skill owns portable lease, trust, sync, and cleanup procedures. This section owns the OpenClaw wrapper and workflow inputs. Routine task-needed Crabbox/Testbox use and task-owned worktrees do not require another confirmation; preserve unrelated work and existing credential, production, budget, and publication boundaries. Run trusted OpenClaw remote proof through the wrapper from the repository root:.crabbox.yaml and the resolved provider before running. The repository
default is blacksmith-testbox, with .github/workflows/ci-check-testbox.yml
owning its prepared environment. Direct providers use
.github/workflows/crabbox-hydrate.yml. Keep the resolved provider unless the
requested proof requires another environment; capacity or hydration failure
does not make a different provider equivalent.
The direct .github/workflows/windows-blacksmith-testbox.yml workflow runs
native Windows. The wrapper’s Blacksmith adapter supports Linux only; explicit
--provider blacksmith-testbox prevents automatic Azure routing but does not
enable Windows support. Blacksmith CLI 0.4.57 targets runner and has no native
username override, so supported CLI sync/run on this Windows image remains
blocked. Native SSH inspection with the per-Testbox key is not CLI end-to-end
proof.
The wrapper checks an executable sibling ../crabbox/bin/crabbox, then PATH,
then the sibling of the Git common checkout. Verify the selected binary and
its source rather than trusting a directory name. If it needs repair or is
missing, use a clean task-owned checkout of
Crabbox, build ./cmd/crabbox into a
task-owned binary directory, and leave other checkouts and the operator’s
installed binary untouched. The existing
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY=1 setting skips the first sibling
candidate; a task binary on PATH then takes precedence over the common-checkout
candidate. A dirty or occupied sibling is not a reason to stop and ask.
For a selected trusted Testbox lane:
node scripts/crabbox-wrapper.mjs warmup --keep --timing-json, save the returned
lease ID, and reuse it with run --id <tbx_id>. Stop the owned lease with
node scripts/crabbox-wrapper.mjs stop --id <tbx_id>; stop has no --timing-json.
- Warm from the task checkout. Claims belong to checkout paths;
--reclaimdeliberately transfers that ownership and never changes repository identity. Sparse staging uses the wrapper’s ownership path. Do not sync or reclaim while another command owns the lease. - Wrapper reuse requires the local SSH key created by Crabbox. A missing key
requires a fresh warmup. Leases created directly by Blacksmith remain usable
through
blacksmith testbox run --id <tbx_id>, not Crabbox wrapper reuse. - Every native Testbox run syncs again, including reused leases.
--no-synccannot preserve a remote baseline. Compare revisions in separate remote worktrees within one synced command; never switch refs in the synced root. - Compound remote shell commands use
bash -lc, notsh -lc; hydration can depend on Bash declarations. Testbox’s workflow owns Chromium, so do not pass Crabbox--browserto that provider. - Keep the lease fingerprint checks described above. No stale-lease override
for release proof. Direct-provider flags such as
--fresh-pr,--full-resync,--script*,--env-helper, capture/download flags, and--stop-afterare not a substitute for the delegated Testbox workflow.
.crabbox/runs and .crabbox/captures outputs together beneath a fresh
.crabbox/wrapper-artifacts/run-* directory before removing that checkout.
Repeated runs retain separate evidence even when native filenames match. The
wrapper prints the old-to-new root mapping; native logs and generated proof may
still reference the old paths. A preservation error fails the wrapper and retains
the temporary checkout at the reported path for manual recovery, preserving the
child’s nonzero exit code. The wrapper rejects symlinks in artifact trees
and destination parents, and copies only regular files and real directories.
Retained files use mode 0600 and new directories use 0700 on POSIX systems.
If preservation fails, recover the outputs from the reported checkout before
removing it; incomplete destination copies are removed.
These are local artifacts, not published or fully sanitized proof. Blacksmith’s
native failure bundle contains captured stdout/stderr and diagnostic metadata;
it does not automatically include remote UI screenshots or reports. Retrieve
those separately before stopping the owned Testbox, and inspect all artifacts
for secrets and private data before sharing.
The native Windows Testbox idle monitor uses the running sshd service’s local
listener ports, not Blacksmith’s externally forwarded SSH port. Established SSH
connections keep the job alive; the ~/.testbox-last-activity modification time
covers short commands between the 30-second polls. Once neither indicates recent
activity, the configured idle timeout still ends the job.
The shared skill’s command placeholders map to the focused commands in this
guide. Its trusted bootstrap is scripts/crabbox-untrusted-bootstrap.sh; the
untrusted path above invokes the installed trusted CLI, never the PR’s wrapper.
For an explicitly selected local-container lane, the existing example image is
node:24-bookworm and the install command is
corepack pnpm install --frozen-lockfile --store-dir .pnpm-store, followed by
the chosen test. Keep --no-hydrate and a repository-local dependency store
when host caches cannot cross filesystems. The OpenClaw broker login endpoint
is https://crabbox.openclaw.ai; normal brokered validation does not require
asking for AWS keys.
Live Gateway, channel, and agent-turn proof uses an isolated
OPENCLAW_STATE_DIR, a free port, and the real user path. Test-only plugin
artifacts may use OPENCLAW_ALLOW_PLUGIN_INSTALL_OVERRIDES=1; that does not make
them official installs. Before sharing WebVNC, inspect a screenshot of the
working app. Keep proof media out of the product repository and compare source
hashes before and after generator runs. If a final timing result is written but
portal synchronization hangs, interrupt only the task wrapper and independently
verify lease cleanup; never stop the operator’s Gateway.
Routine local order
pnpm test:changedfor changed-scope Vitest proof.pnpm test <path-or-filter>for one file, directory, or explicit target.pnpm testonly when you intentionally need the full local Vitest suite.
--help or -h request.
Compound requests, including --help --no-help, follow native Vitest option semantics.
An existing UI directory target stays scoped to that directory, including when
combined with explicit E2E test files. Tests retain their owning shared, isolated,
or browser lane. UI source/support-file targets that need whole-lane coverage
(such as shared styles or setup files) still use that broader fallback; use a
directory or explicit test files when you want a bounded run.
When a one-shot routed run targets only explicit test-file paths, each selected
Vitest invocation must discover at least one test file. Excluding every selected
file fails even when the lane normally permits empty runs. To allow that outcome
intentionally, use pnpm test <test-file-path> -- --passWithNoTests. Use
--passWithNoTests=false to require nonempty discovery explicitly. Broader
selectors and source-derived selections retain their lane defaults.
An explicit --config run through scripts/run-vitest.mjs keeps its stricter
named-file policy and does not permit empty named-file runs. Plugin
--allow-no-tests and --allow-empty-after-exclude controls are unchanged.
Codex and other linked/sparse worktrees can run local tests and checks. When the
dependency install is ready, use the normal commands above. If pnpm would
reconcile a shared install, use the direct Node harnesses to bypass that
package-manager preflight:
- Bounded focused proof with ready dependencies:
node scripts/run-vitest.mjs <path-or-filter>. - Changed typecheck/lint/guard proof:
node scripts/check-changed.mjs.
node scripts/crabbox-wrapper.mjs
directly. Avoid local pnpm crabbox:run in linked worktrees because pnpm may
reconcile dependencies before the remote wrapper starts.
Core commands
Maintained JavaScript tooling wrappers and root package commands load TypeScript throughscripts/tsx.mjs, using tsx’s ESM entry. This preserves native loading of
compiled ESM plugins and their import-only dependencies, including when loaded
through require(). Source TypeScript imports and tsconfig path aliases remain
available.
These launchers retain tsx’s in-process transform cache and Node’s module cache.
They skip tsx’s shared disk cache before the loader starts, and child tooling
inherits that policy. This cache policy does not clean
existing temporary directories, Node or Vitest caches, or other global caches. Standalone
pnpm ui:build keeps native startup and applies the same preload to its post-build
validators; it does not require TSX_DISABLE_CACHE in the invoking shell. Raw
external tsx and node --import tsx invocations outside these launchers are unchanged.
Control UI builds report size budgets without enforcing them. Run
pnpm ui:check-performance after a build to enforce absolute budgets, or
pnpm ui:check-performance:base <base-commit-sha> to build and compare both
revisions with the same toolchain. See Control UI size budgets.
Source tests and subprocess builds
Non-watch runs throughpnpm test or scripts/run-vitest.mjs keep Vitest tests
and runtime parents on TypeScript. Importing a declared subprocess entrypoint
compiles the fixed test entry set and its workspace dependencies into one fresh
invocation directory under .artifacts/vitest-workers/.
The ten declared application entries run as plain Node JavaScript without a
TypeScript loader: SQLite read-only snapshots, database verification, Tailscale
route ownership, the service relay, its POSIX and Windows anchors, the memory
plugin’s KNN child, session transcript archive and reconciliation workers, and
managed GitHub credential resolution. The same generation also compiles the fake-backend TUI
fixture’s four runtime roots together: the real TUI, embedded reply producer,
reply metadata reader, and outbound normalizer. Shared chunks preserve their
module and WeakMap identity. Generated TUI fixtures remain .mts files: Node
launches them with --import tsx for their own syntax, while Bun handles that
syntax natively without the Node loader. Only their runtime imports change.
Existing package build entry paths and Vitest source parents stay unchanged. The
CLI fork-recovery regression also compiles the real CLI entry and its concurrent
rebind’s session accessor and binding helper together. Both processes use the same
runtime graph while retaining the durable-write race and process-exit assertions.
Doctor process output tests with bundled plugins disabled reuse that compiled CLI
inside one lazily created package fixture per test run, keeping real UI checks on
fixture-owned assets and each scenario’s state separate. Standalone and watch runs
use live source inside the same fixture. Other
Worker-thread entries and arbitrary source CLI fixtures remain outside this declared set.
The session-title and child-link retention tests declare their title-reader,
session-utils, and listing roots in this same generation. Each fresh
heap-measurement child runs their JavaScript without spending its execution
deadline on TypeScript imports.
Preparation is lazy across both projects and shards. Config imports, listing
tests, and tiny tests that do not import these declarations do not load the
subprocess compiler or compile workers. A shard that needs a declaration requests the
outer runner’s single build through its existing Node IPC channel during module
collection, before fixture hooks and readiness deadlines. Every finite invocation
that needs a declaration pays for this fixed entry set; preparation timing is
reported separately from child execution. The runner starts one short-lived native
Node or Bun compiler child and joins it before returning the verified manifest to
borrowers. The compiler module graph lives in that child, not the long-lived runner
or Vitest worker. No shard can select a different build graph or adopt another invocation’s output. The outer
runner retains the generation until child close and process-group cleanup
finish, then verifies it before reporting success. Verification reads every recorded
input and output with bounded asynchronous I/O, keeping the runner responsive during
large shutdown scans. The invocation owner verifies each borrower’s preparation
before replying and verifies again after all borrowers close; Vitest does not repeat
these scans inside each shard or during its concurrent pool shutdown. Standalone
Vitest and watch runs retain source execution: compilation, verification, and artifact
deletion require the repository runner’s ownership.
A lost owner or failed build fails the run.
Disposal cancels pending compilation and joins it, every borrower, and outstanding
preparation requests before asynchronously removing the directory. Signal handlers
remain active through removal, even when large generations take time to delete.
Borrower completion does not wait for compilation, so an early child exit can reach that cancellation path. An uncertain compiler or borrower join retains the
generation and fails the run. Abnormal termination can also leave an unused
directory; later runs never adopt it.
Every preparation compiles current source; checkout dist/ is neither an input
nor a fallback. Build errors, missing artifacts, and changes to recorded build
inputs fail the run. Compilation includes the native subprocess fixtures before
they impose resource limits. Third-party dependencies remain external except for
the always-bundled OpenClaw packages. fs-safe remains external so its native loader
resolves the optional platform package from fs-safe’s own dependency scope, including
nested pnpm installs. Compiled workers use that same installed package; they do not
copy native binaries. The default stays off, and the existing off/auto/require
opt-ins retain their behavior. Sealed portable worker bundles use guarded JavaScript
only and explicitly disable native loading.
Watch mode deliberately keeps the existing live-source path, including tsx for
Node subprocesses and native TypeScript handling for Bun. It creates no prepared generation, so a new child launch
reads current source rather than reusing a compiled snapshot. Existing Vitest
watch dependency tracking still determines when tests rerun.
Test wrapper runs end with a short [test] passed|failed|skipped ... in ... summary; Vitest’s own duration line stays the per-shard detail.
A failed invocation ends with one [test] FAILED (exit N) line after child
processes, cleanup, and report publication settle. Direct run-vitest.mts calls
use [vitest] instead. Nested runners retain their diagnostics and exit status;
the top-level CLI owns the final failure line. Successful runs emit no failure
trailer.
pnpm check:changed also runs the mobile protocol-event coverage guard when
changes affect the gateway event catalog or constants, scanned mobile sources,
coverage declarations, or the guard, its execution helpers, and its routing.
All-lane checks include it too. Every gateway event must have a handler or an
explicitly approved non-consumption declaration for each mobile client. To run
only this guard, use pnpm check:protocol-coverage.
For native app changes, pnpm check:changed uses platform scope to select lint:
Android selects pnpm android:lint (the Gradle ktlint checks), while Apple app
changes retain Swift lint. Android-only changes do not select Swift lint or its
missing-tool notice. Android framework/resource lint and runtime tests remain
separate checks; Kotlin lint does not replace them.
Remote filesystem fixtures that execute GNU stat and readlink run locally
only on Linux. The shared leading-@ file-tool scenario
also runs against a portable remote-only bridge on every platform. Native
Python helper coverage remains separate, including macOS; these fixture gates
do not restrict the SSH backend’s Gateway host.
Shared test state and process helpers
build-all, standalone tsdown builds, tsgo, SDK declaration preparation,
package-boundary checks, and dependent lint use checkout-local ownership at
.artifacts/dist-artifacts.lock. Ownership spans
cleanup, generation, cache restoration, and the checks consuming those outputs;
independent checkouts remain independent. Competing commands print a waiting
message and wait for a live owner without an acquisition deadline. Compiler and
build execution timeouts are unchanged. Standalone tsgo runs serialize, including
source-only checks; the core test shard runner retains its explicit concurrency
inside one owner. Do not delete dist manually while these commands are running.
An abrupt owner or nested wrapper exit, or unverified child cleanup, retains the
lock. A missing or unverifiable owner PID, or a recorded child-cleanup failure,
fails acquisition promptly without reclaiming anything. PID death does not prove
detached descendants stopped. Before manually removing an abandoned lock directory,
inspect its owner.json and verify all associated build, compiler, and lint
processes, including detached descendants, have stopped; then retry the command.
Lint reports its final failure on stderr after child joins and artifact ownership
have settled, including retained ownership when cleanup is uncertain. Standalone
Oxlint and its shard CLI end with [oxlint] FAILED (exit N); pnpm lint owns the
whole pipeline and ends with one [lint] FAILED (exit N) instead. Shard progress
distinguishes passed from failed (exit N), and stdout remains available for
machine-readable tool output. Successful runs have no failure trailer. Signals
forwarded during child execution and shard timeouts fail the command; whole-host
loss or SIGKILL of the reporting process can prevent a final line.
Local plugin lint consumes native SDK declarations in packages/plugin-sdk/dist.
The dedicated package-boundary compiler also consumes seven plugin API trees in
.artifacts/extension-package-boundary/plugins. Each declaration and compile
owner validates its consumed source content, inherited config, selected compiler,
and complete output inventory. Unrelated existing source or test edits retain
cache hits. Resolution-topology changes invalidate conservatively, including new
module candidates outside declared roots. Stale declarations get a full native
emit after clearing only their build-info file; the successful emitted inventory
then drives obsolete declaration pruning. Missing or tampered outputs invalidate
the owner. The content records live under
.artifacts/extension-package-boundary, outside packaged build cleanup. A warm run validates the records without emitting declarations.
Native declaration and package-boundary records accept only checkout-owned input
realpaths, including compiler libraries, inherited config, dependency links, and
package manifests. Local pnpm links remain supported when their targets stay
inside the checkout. The tsgo wrapper does not create or reuse a shared external
install; invocations from subdirectories still use the containing checkout as
the ownership boundary. Declared checkout junctions and platform path aliases map
to the same native root for validation and actual snapshot reads. Native resolution
itself is not sandboxed: an ancestor install can still enter a successful compiler
receipt. The owner then fails with Declaration input escapes checkout, without
publishing a success record or pruning obsolete declarations. Warm records use
the same input check. Use a standalone checkout outside ancestor installs with
its own pnpm install when this occurs; do not remove the ancestor installation
or weaken input checks.
Packaged SDK declarations belong to one staged owner shared by full, package, and
ciArtifacts builds. It serializes the two canonical tsdown SDK groups on a miss
and caches their complete staged generation. Each successful compiler supplies its
source membership through a private staged receipt; missing receipts or inputs
changed during compilation prevent publication. The shared input snapshot policy
validates consumed bytes, inherited configuration, generator and manifest inputs,
and resolution topology without starting a compiler on hits. Cache hits restore
into fresh staging and pass the same entry and relative declaration closure checks
before publication.
All tsdown declaration builds (the eight SDK/unified groups, workspace packages,
and the AI package) resolve source and dependency realpaths within their checkout.
Ancestor installs are invisible to TypeScript lookup; selected declaration paths
that escape through symlinks or bundler resolution fail the build. Each checkout
needs its own installed declaration inputs, including compiler libraries. Local
pnpm links are supported when their targets remain inside the checkout; shared
external installs are not. Actual compiler receipts remain unfiltered, and input
changes still prevent publication. Runtime module resolution is unchanged;
native tsgo uses the separate receipt-admission policy above.
Local preparation never overwrites packaged declarations or writes workspace
forwarding bridges.
Plugin SDK declaration preparation and scripts/run-tsgo.mjs require child work
to finish before reporting success. On POSIX, each verifies its own managed
process group: leftover children are terminated and the command fails instead of
allowing artifact stamps or downstream checks to proceed. Windows retains normal
joined-launcher completion because strict group verification is unsupported there.
This does not detect descendants that deliberately leave the managed groups.
run-vitest (including project shards), plugin batches, test-live
(including live shards), run-vitest-profile, and the TUI PTY watcher give each
Vitest invocation an owned temporary namespace through TMPDIR, TMP, and TEMP.
Before Vitest starts, isolated invocations also receive native HOME and
USERPROFILE inside that namespace. This protects home fallbacks used by worker
threads, named builtin imports, and import-time captures; changing only a worker’s
JavaScript process.env does not change native thread home lookup. Per-worker and
per-test fixture homes remain separate. Installed Corepack and Playwright browser
caches retain their caller-selected locations.
Live-aware setup still loads the original profile and stages live state when
requested. A bounded invocation artifact carries the original home to that setup;
it does not grant live access, and hermetic setup never consults it. Known
hermetic selections ignore ambient live and real-home flags. Known wholly
live-aware selections retain explicit OPENCLAW_LIVE_USE_REAL_HOME behavior.
An explicitly real-home live invocation is refused before config loading if its
selection mixes home policies or cannot be classified, including custom configs
and ambiguous project selectors. Run hermetic tests without LIVE,
OPENCLAW_LIVE_TEST, OPENCLAW_LIVE_GATEWAY, and OPENCLAW_LIVE_USE_REAL_HOME
using node scripts/run-vitest.mjs <test-path>, then run the intended live
selection separately using node scripts/test-live.mts -- <live-test-path>.
The launcher does not split runs or change watch, filter, or report semantics.
The namespace contains isolated homes, their JIT caches, SDK/shared-home allocation
roots, and fallback SQLite state; its lifetime spans shared-worker files and module
resets. On POSIX detached launches, the parent removes
only that namespace after its child process group has stopped, output pipes
have closed, and nested resource owners have released their pending claims,
including passing and failing runs, child crashes, caught SIGINT/SIGTERM
signals, and watchdog termination where supported. Explicit state, profile output,
and mirror artifacts outside the namespace remain untouched. Failed or unverified
group joins or unresolved nested claims retain the namespace and report the exact
path for manual recovery. Nested namespaces, fixture lifetimes, and managed commands
register ephemeral filesystem ownership before admitting work. Release requires
positive completion evidence; caught cleanup failures, module resets, worker exit,
or an intermediate runner crash cannot release a pending claim or its ancestors.
Managed commands keep their existing close-based completion contract unless strict
tree verification is requested; failed finalization never releases ownership.
Stop all remaining writers before manually removing the reported exact directory.
Windows and non-detached launches allocate the same isolated native home, but retain
their namespace and enclosing claims with a diagnostic after child exit and pipe
closure because descendant completion cannot be verified. Raw external invocations do not gain
this boundary. Forced parent or supervisor death (such as SIGKILL) can prevent
cleanup; unregistered descendants that intentionally escape the owned group remain
outside this contract. The wrappers do
not sweep old directories or infer ownership from names, ages, or PIDs.
This is home isolation, not a filesystem sandbox: explicit absolute paths,
os.userInfo() account lookup, children with stripped or replaced home variables,
and intentionally real-home live execution remain outside its protection.
src/test-utils/openclaw-test-state.ts: use from Vitest when a test needs an isolatedHOME,OPENCLAW_STATE_DIR,OPENCLAW_CONFIG_PATH, config fixture, workspace, agent dir, or auth-profile store.pnpm test:env-mutations:report: non-blocking report of tests/harnesses that mutateHOME,OPENCLAW_STATE_DIR,OPENCLAW_CONFIG_PATH,OPENCLAW_WORKSPACE_DIR, or related env keys directly. Use it to find migration candidates for the shared test-state helper.test/helpers/openclaw-test-instance.ts: process-level E2E tests needing a running Gateway, CLI env, log capture, and cleanup in one place.- Docker/Bash E2E lanes that source
scripts/lib/docker-e2e-image.shcan passdocker_e2e_test_state_shell_b64 <label> <scenario>into the container and decode it withscripts/lib/openclaw-e2e-instance.sh; multi-home scripts can passdocker_e2e_test_state_function_b64and callopenclaw_test_state_create <label> <scenario>in each flow.node --import tsx scripts/lib/openclaw-test-state.mts -- create --label <name> --scenario <name> --env-file <path> --jsonwrites a sourceable host env file (the--beforecreatekeeps newer Node runtimes from treating--env-fileas a Node flag). Lanes that launch a Gateway can sourcescripts/lib/openclaw-e2e-instance.shfor entrypoint resolution, mock OpenAI startup, foreground/background launch, readiness probes, state env export, log dumps, and process cleanup.
createOpenClawTestState selects and owns temporary paths and process environment
selectors. It is not filesystem sandboxing and does not stop external producers.
Await its asynchronous restoreEnv(); stop and join required producers before
restoring selectors or removing state. Runtime reproductions of state-selection
leaks require enforced storage isolation, such as a VM or container without access
to operator stores, not merely temporary HOME or state-directory overrides.
Control UI, TUI, and extension lanes
-
Control UI E2E:
pnpm test:ui:e2eruns the Vitest + Playwright lane, usually against a mocked Gateway WebSocket. Four resource groups retain two execution phases:ui-e2e-bundledandui-e2e-standalonerun first with at most two workers total;ui-e2e-serialandui-e2e-serial-standalonethen share one worker. The two bundle consumers lazily share one temporary UI bundle/preview until the invocation closes. Standalone projects own their fixture, source, or custom-build servers; selecting only standalone suites avoids the shared bundle build. Every selected project receives Chromium metadata, and new E2E files default to parallel bundled ownership. The root config retains the full discovery inventory:ui/src/**/*.e2e.test.tsplus the QA Lab media-transcript and OpenClaw-delegation real-Gateway suites. Shared mocks/controls live inui/src/test-helpers/control-ui-e2e.ts. Some suites start isolated real Gateways;OPENCLAW_UI_E2E_SKIP_REAL_GATEWAY=1excludes them.pnpm test:e2eincludes this lane, with no additional CI jobs for resource groups. Use Testbox/Crabbox only when clean Linux/browser parity is part of the proof. In a linked worktree,node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/e2e/chat-flow.messaging.e2e.test.tsavoids pnpm dependency reconciliation for a targeted local run. -
Control UI real-Gateway approval proof: Check default and explicit Full Access delegation against an isolated Gateway with a mock provider. Build the runtime before running the targeted proof:
-
TUI PTY tests:
node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.tsruns the fast fake-backend PTY lane.OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1orpnpm tui:pty:test:watch --mode localruns the slowertui --localsmoke, which mocks only the external model endpoint. CI also setsOPENCLAW_TUI_PTY_USE_BUILT_CLI=1after buildingdist/; use that flag only when exact-head built artifacts already exist. Assert stable visible text or fixture calls, not raw ANSI snapshots. -
pnpm test:extensionsandpnpm test extensionsrun all extension/plugin shards. Heavy channel plugins, the browser plugin, and OpenAI run as dedicated shards; other plugin groups stay batched.pnpm test extensions/<id>runs one bundled plugin lane. -
Browser native host:
node scripts/run-vitest.mjs extensions/browser/src/browser/extension-install.native-host.e2e.test.tsruns the real native messaging launcher on macOS or Linux against built dist with synthetic installation state; it does not launch Chrome or a Gateway. Windows skips this POSIX process proof because native bootstrap uses manual pairing there. The E2E owner prepares artifacts before workers. With an already-built candidate, prefix the command withOPENCLAW_E2E_USE_PREBUILT_DIST=1to reuse it; missing artifacts fail the test. This case belongs topnpm test:e2e, not the browser source shard or untargetedpnpm testunit suite. Linux CI runs it explicitly inbuild-artifactsand validates a JSON report proving the exact named test passed. The workflow skips only frozen historical checkouts missing this test file; that skip is unavailable proof, not a pass or coverage. -
Source files with sibling tests map to that sibling before falling back to wider directory globs. Helper edits under
src/channels/plugins/contracts/test-helpers,src/plugin-sdk/test-helpers, andsrc/plugins/contractsuse a local import graph to run importing tests instead of broad-running every shard when the dependency path is precise. -
Contract directory targets fan out to their contract lanes:
pnpm test src/channels/plugins/contractsruns the four channel contract configs andpnpm test src/plugins/contractsruns the plugin contracts config, since the genericchannels/pluginsprojects excludecontracts/**. -
auto-replysplits into three dedicated configs (core,top-level,reply) so the reply harness does not dominate the lighter top-level status/token/helper tests. -
Selected
plugin-sdkandcommandstest files route through dedicated light lanes that keep onlytest/setup.ts, leaving runtime-heavy cases on their existing lanes. -
Base Vitest config defaults to
pool: "threads"andisolate: false, with the shared non-isolated runner enabled across repo configs. -
pnpm test:channelsrunsvitest.channels.config.ts.
Real-Gateway Control UI fixture lifetimes
UsecreateControlUiE2eSuite from
ui/src/e2e/control-ui-e2e-suite.test-support.ts for real-Gateway browser fixtures.
suite.define(...) owns the native hooks. Each native it passes its test context
to suite.runScenario(context, ...), which owns acquisition, the test body, and
finalization before another case starts. Acquire and close browser contexts through
suite.newBrowserContext and suite.closeBrowserContext so late acquisitions and
pending closes remain owned.
Retain test state immediately after createOpenClawTestState resolves, including
when later config writes, imports, or startup fail. Hold original startup promises,
not just their timeout wrappers. Close required producers before releasing state.
For producers shared across cases, as in the MCP and auth suites, use the suite’s
resources.run, resources.close, and resources.release callbacks instead of
independent beforeAll/afterAll cleanup. Resource acquisition follows shared
server/browser acquisition; teardown joins cases and browser cleanup, closes
required producers and servers, and releases state only after those closes succeed.
Failed or unjoined cleanup retains selectors and state, blocks later cases using
this suite owner, and leaves native Vitest to terminate and join the isolated fork. Do not
swallow close failures or restore the environment beneath unfinished work. The
lifetime owner preserves existing hook, test, and action budgets.
Gateway close joins received WebSocket work and asynchronous connection cleanup,
including cooperating background refreshes registered at their producer with
trackAsyncWork. Connection-dependent worker sidecars must stop successfully
before supervisor transports or other dependencies close; failure retains those
dependencies and rejects shutdown. Register the actual operation, not just its
response or timeout wrapper; cache eviction does not end its lifetime. withOpenClawTestState likewise
joins registered callback descendants before releasing state. MCP requests observe
both caller cancellation and their closing work owner, so shutdown cancels pending
requests before joining handlers and disposing transports. These scopes do not
automatically track arbitrary detached work or replace native test-timeout ownership.
Other Gateway subsystems can retain documented bounded shutdown behavior, so close
is not a guarantee of universal subsystem or descendant-process quiescence.
Retained mocked Control UI proof
For startup ownership changes, exercise authenticated hello before browser recovery migration finishes. Project and environment discovery can start from hello; migration completion must not refetch those catalogs or invalidate an admitted start. Keep changed-owner, process-restart, and late-result fences covered separately. Count storage reads by key around rerenders, typing, and streaming without recording credential values. Compare route payload bytes and loaded module closures separately from timings; CSS ownership changes also need retained screenshots and computed-style or geometry checks across New session and Chat. Ordinary mocked browser screenshots, recordings, and reports use fresh directories for each test attempt or standalone capture invocation. The Node-onlycreateControlUiE2eArtifactDir(scope, parentDir?) helper in
ui/src/test-helpers/control-ui-e2e-artifacts.ts prints the actual allocated path.
An explicit parent wins; otherwise it uses the trimmed existing
OPENCLAW_UI_E2E_ARTIFACT_DIR, then the repository’s .artifacts/control-ui-e2e
parent. Existing feature-specific directory controls and script output arguments
select parents, with unique children beneath them. Explicit screenshot filename
controls preserve the basename and print the relocated path.
Keep capture gates independent from allocation: OPENCLAW_CAPTURE_UI_PROOF,
OPENCLAW_UI_E2E_RECORD, and output-presence gates retain their existing meanings.
Allocate during scenario execution or beforeEach, and pass the same owner to
shared capture helpers so screenshots, reports, and video stay together. Distinguish
stage names within an attempt. Close the browser context before finalizing video.
Successful and failed evidence is retained. Cleanup is manual: remove only exact
directories that you own and have finished reviewing. Never recursively delete
the shared parent before a replay. Disposable build/media fixtures and temporary
raw video have their own cleanup. New captures cannot recover overwritten evidence;
do not describe a replay as recovery of lost files.
Timeout diagnostics allocate fresh children beneath the existing
OPENCLAW_UI_E2E_DIAGNOSTIC_DIR or default timeout directory, keeping each PNG and
JSON report together. Their ci.shardIndex and ci.vitestShardCount fields record
VITEST_SHARD_INDEX and VITEST_SHARD_COUNT, respectively, as supplied by normal
CI. Missing values remain null; manual and separate release E2E invocations do
not infer this metadata from Vitest’s --shard argument.
Mantis allocates an invocation directory for setup logs,
capture attempts, and its report; the builder preserves each attempt’s relative
paths and refuses to overwrite an existing report.
The real-Gateway auth transport suite also allocates one fresh directory per
suite invocation. Its screenshots wait for meaningful content and the presentation
owner’s finite entrance or resize animations, while perpetual descendant activity
continues.
Separate output owners remain: other real-Gateway suites, chat-outbox-*, and
chat-attachment-read-lifecycle. Do not assume those owners have the ordinary
mocked proof retention guarantee.
Screenshots during Chromium recordings
The session-host command-state real-Gateway proof usespage.screenshot({ path })
without clip or fullPage: true, keeping its existing viewport, recording size,
waits, and animation options. This path was verified on Linux with Playwright
1.62.1 and full Chrome for Testing 151.0.7922.34.
Other recording owners have not been migrated or certified by this proof; some
still use locator or full-page screenshots. This is not a suite-wide capture
policy. Verify each owner’s screenshot content and finalized video before
changing its capture mode. When using the verified viewport path, crop any
element-only PNG outside the browser. Cropping cannot recover missing content
from an already-corrupted recording.
In a macOS arm64 reproduction with Playwright 1.62.1 and its bundled full Chrome
for Testing 151.0.7922.34, locator.screenshot() and
page.screenshot({ clip }) caused small screencast frames. The element appears at
the video origin with gray elsewhere, even though the PNG, DOM geometry, and
functional assertions are correct. fullPage: true is not a general workaround:
a document larger than the viewport can instead produce a shrunken page with
gray padding. Unclipped viewport captures preserved the recording in the same
synthetic reproduction; other browser versions and platforms require their own
verification.
This is an upstream capture limitation, not a Gateway or context-cleanup failure.
Chromium’s screenshot handler
temporarily changes the shared view size and restores it after capture; its
screencast producer can observe the intermediate surface.
Playwright’s recorder
pads undersized frames with gray. Closing the context finalizes the video but
does not repair those frames. Do not filter out bad frames or change UI behavior
to conceal this limitation.
Verify finalized video content around every capture, not just its dimensions or
the success of locator assertions. For a dependency upgrade, reproduce with a
synthetic page containing an offset small element, compare element, clipped,
viewport, and oversized full-page screenshots, and inspect every decoded frame.
Keep real host/profile footage local; inspect public proof for synthetic-only
content before sharing. Correct PNGs remain useful still-image proof, but a
corrupted video is not continuous-flow proof.
Gateway and E2E
- Gateway tests are included in the untargeted
pnpm testfull suite; run them alone withpnpm test:gateway. pnpm test:e2e: repo E2E aggregate =pnpm test:e2e:gateway && pnpm test:e2e:agent-plugin-gateway && pnpm test:ui:e2e.pnpm test:e2e:gateway: gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults tothreads+isolate: falsewith one worker invitest.e2e.config.ts; opt into parallelism withOPENCLAW_E2E_WORKERS=<n>(capped at 16), and enable verbose logs withOPENCLAW_E2E_VERBOSE=1. Broad runs prepare the shared runtime once, then use four sequential Vitest shards in fresh processes to bound worker memory. The worker limit applies within each process; ordinary test failures are retained while remaining shards finish. Explicit filters, watch mode, caller-supplied shards, coverage, and report-output options keep one direct invocation.pnpm test:live: provider live tests (Claude/Minimax/DeepSeek/z.ai/etc, gated by*.live.test.ts). Requires API keys andLIVE=1(orOPENCLAW_LIVE_TEST=1) to unskip; verbose output withOPENCLAW_LIVE_TEST_QUIET=0.
Full Docker suite (pnpm test:docker:all)
Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into /app, then runs Docker smoke lanes through a weighted scheduler. scripts/package-openclaw-for-docker.mjs is the stable local/CI package packer entrypoint and validates the tarball plus dist/postinstall-inventory.json before Docker consumes it.
- Bare image (
OPENCLAW_DOCKER_E2E_BARE_IMAGE): installer/update/plugin-dependency lanes; mounts the prebuilt tarball instead of copied repo sources. - Functional image (
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE): normal built-app functionality lanes. - Lane definitions:
scripts/lib/docker-e2e-scenarios.mts. Planner:scripts/lib/docker-e2e-plan.mts. Executor:scripts/test-docker-all.mjs. node scripts/test-docker-all.mjs --plan-jsonemits the scheduler-owned CI plan (lanes, image kinds, package/live-image needs, state scenarios, credential checks) without building or running Docker.
Env var pattern for resource caps is
OPENCLAW_DOCKER_ALL_<RESOURCE>_LIMIT (resource name uppercased, non-alphanumerics collapsed to _).
Other behavior: the runner preflights Docker by default, cleans stale OpenClaw E2E containers, shares provider CLI tool caches between compatible lanes, and stops scheduling new pooled lanes after the first failure unless OPENCLAW_DOCKER_ALL_FAIL_FAST=0 is set. If one lane exceeds the effective weight/resource cap on a low-parallelism host, it can still start from an empty pool and run alone until it releases capacity. Per-lane logs, summary.json, failures.json, and phase timings write under .artifacts/docker-tests/<run-id>/; use pnpm test:docker:timings <summary.json> to inspect slow lanes and pnpm test:docker:rerun <run-id|summary.json|failures.json> to print cheap targeted rerun commands.
Notable Docker lanes
Sandbox compatibility lanes
Local PR gate
For local PR land/gate checks, run:pnpm check:changedpnpm checkpnpm check:test-typespnpm buildpnpm testpnpm check:docs
pnpm test flakes on a loaded host, rerun once before treating it as a regression, then isolate with pnpm test <path/to/test>. For memory-constrained hosts:
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm testOPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache pnpm test:changed
JSON reports across native processes
For a multi-project or chunked run, explicitly request native JSON with an output file, for example:<output>.reports-<unique> directory. Keep that directory:
it contains original reports, per-attempt coverage files when coverage is enabled,
and an index.json with child exit codes, signals, timeouts and unstarted work.
Only the accepted retry attempt contributes to the aggregate.
The aggregate preserves the accepted case inventory, but is not a lossless
replacement for the originals. Native merging does not restore snapshot summaries
or JSON coverageMap, and its startTime is the merge time. Passing snapshot tests
still succeed. Read native originals for those details and the index for process
outcomes: JSON success does not encode every wrapper or unhandled-error failure.
Separate built-in coverage reports remain per attempt in the companion directory.
Custom coverage providers/reporters and coverage reporter tuple options require
separate invocations with unique destinations.
A complete failed-test aggregate is retained with a failing command exit. Missing
or invalid evidence, cancellation, unstarted required work, or publication failure
does not publish a complete aggregate; an existing output file is not proof of the
new run. The diagnostic prints the retained report-set location. Report sets are
not automatically swept.
Overlapping selections can share native task IDs, so merging them can replace
independent failure details even when case counts match. Such report sets retain
their originals and fail publication. Select each configuration once, or run
overlapping selections separately with distinct output files.
This ownership applies to explicit CLI JSON file requests with named, file-based
Node projects and native console reporters. Scalar --outputFile and
--outputFile.json both work. Config-owned reporter options, other file formats,
custom reporters and inline/browser project composition require separate native
invocations with unique output destinations. Do not assume those outputs are
aggregated. Single-process and console-only runs keep their existing native behavior.
Native help and other non-test controls stay with the child CLI and do not allocate
report sets. run --version still runs tests, as it does in native Vitest.
Config-only reporters are not intercepted: multiple children can still overwrite
the same configured file. Run those configurations separately with distinct paths;
adding --reporter=json alone does not override a reporter tuple’s own outputFile.
Test performance tooling
pnpm test:perf:imports: enables Vitest import-duration + import-breakdown reporting, while still using scoped lane routing for explicit file/directory targets.pnpm test:perf:imports:changedscopes the same profiling to files changed sinceorigin/main.pnpm test:perf:changed:bench -- --ref <git-ref>benchmarks the routed changed-mode path against the native root-project run for the same committed git diff;pnpm test:perf:changed:bench -- --worktreebenchmarks the current worktree change set without committing first.pnpm test:perf:profile:mainwrites a CPU profile for the Vitest main thread;pnpm test:perf:profile:runnerwrites CPU + heap profiles for each unit worker. Both print their output directory (a temporary directory by default). Use-- --output-dir <dir>orOPENCLAW_VITEST_PROFILE_DIRto retain profiles at a chosen location.pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json: runs every full-suite Vitest leaf config serially and writes grouped duration data plus per-config JSON/log artifacts. Full-suite reports isolate files by default so retained module graphs and GC pauses from earlier files are not charged to later assertions; pass-- --no-isolateonly when intentionally profiling shared-worker accumulation.pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.jsoncompares grouped reports after a performance-focused change.- Full, extension, and include-pattern shard runs update local timing data in
.artifacts/vitest-shard-timings.json; later whole-config runs use those timings to balance slow and fast shards. Include-pattern CI shards append the shard name to the timing key, which keeps filtered shard timings visible without replacing whole-config timing data. SetOPENCLAW_TEST_PROJECTS_TIMINGS=0to ignore the local timing artifact. pnpm ci:timings:refit: regenerate committedconfig/ci-test-timings.jsonfrom the last five successful main CI runs; add--dry-runto preview the changed-entry table. This file owns per-file UI E2E and per-profile compact-group weights, unlike the gitignored.artifacts/vitest-shard-timings.jsonwhole-config timing cache. Independent CI shards use only the committed weights, never that cache. See CI timing refits for the daily refresh and sampling rules.
forks or threads pool, isolation, environment, and custom runners extending Vitest’s TestRunner. Capture starts in a Node preload before Vitest worker imports, spans all files assigned to that worker, and finishes both profile files in awaited worker cleanup before teardown is acknowledged. It does not depend on exit-time profile flushing. Root global setup configures every selected project without replacing its reporters or setup. Main capture spans Vitest/Vite startup through run completion and close. Process termination before cleanup, bootstrap failures before runner construction, and teardown timeouts can still prevent output. Browser/VM pools, custom runners without onCleanupWorkerContext, and additional native --cpu-prof/--heap-prof flags are rejected for runner profiling.
Forward Vitest options after the profiler separator. Forwarded options use Vitest’s native CLI validation before loading config. Config-only settings, such as runner and globalSetup, belong in the Vitest config file, not CLI flags. For example:
pnpm test:extensions:memory profiles built plugin index entries from dist/extensions (including nested dist output) and package-local extensions/<id>/dist output; TypeScript source entries are excluded. Root artifacts take precedence when both builds exist. Selecting an already-built plugin with --extension <id> reuses its output without requiring unrelated plugin builds; build the plugin package first if its output is not supplied by pnpm build.
Native imports also need the plugin’s declared dependencies and a resolvable openclaw host package. The profiler does not install or link dependencies: missing dependencies remain import failures in the JSON report and cause a nonzero exit.
Benchmarks
Model latency (scripts/bench-model.ts)
Model latency (scripts/bench-model.ts)
MINIMAX_API_KEY, MINIMAX_BASE_URL, MINIMAX_MODEL, ANTHROPIC_API_KEY. Default prompt: “Reply with a single word: ok. No punctuation or extra text.”CLI startup (scripts/bench-cli-startup.ts)
CLI startup (scripts/bench-cli-startup.ts)
startup:--version,--help,health,health --json,status --json,statusreal:health,status,status --json,sessions,sessions --json,tasks --json,tasks list --json,tasks audit --json,agents list --json,gateway status,gateway status --json,gateway health --json,config get gateway.portall: both presets combined
sampleCount, avg, p50, p95, min/max, exit-code/signal distribution, and max RSS per command. --cpu-prof-dir / --heap-prof-dir write V8 profiles per run.Saved output: pnpm test:startup:bench:smoke writes .artifacts/cli-startup-bench-smoke.json; pnpm test:startup:bench:save writes .artifacts/cli-startup-bench-all.json (runs=5 warmup=1). Checked-in fixture: test/fixtures/cli-startup-bench.json, refreshed by pnpm test:startup:bench:update, compared by pnpm test:startup:bench:check.Gateway startup (scripts/bench-gateway-startup.ts)
Gateway startup (scripts/bench-gateway-startup.ts)
Gateway startup, restart, and agent concurrency benchmark fixtures use temporary home and state directories, loopback binding, and Case ids:
discovery.mdns.mode: "off" so synthetic Gateways do not advertise on the LAN, including on macOS.Defaults to the built CLI entry at dist/entry.js; run pnpm build first. Pass --entry scripts/run-node.mjs to measure the source runner instead, and keep those results separate from built-entry baselines.default, skipChannels (channel startup skipped), oneInternalHook, allInternalHooks, fiftyPlugins (50 manifest plugins), fiftyStartupLazyPlugins (50 startup-lazy manifest plugins), incidentDatabase, incidentNullMetadata, incidentWorkspace, incidentPackagedPlugins, and incidentCombined.The incident cases are opt-in because each sample builds an isolated, non-sensitive load fixture: current global and agent databases, 100,000 retained audit rows with freelist fragmentation, eight agent workspaces containing 80,000 files (about 800 MB), and the packaged plugin inventory. Run the combined case only on a clean machine with enough free disk space; the fixture directory is removed after each sample. incidentCombined fails when /healthz p95 reaches 30 seconds or /readyz p95 reaches 60 seconds.Output includes first process output, /healthz, /readyz, HTTP listen log time, Gateway ready log time, CPU time, CPU core ratio, max RSS, heap, startup trace metrics, event-loop delay, and plugin lookup-table detail metrics. The script sets OPENCLAW_GATEWAY_STARTUP_TRACE=1 in the child Gateway environment./healthz is liveness (HTTP server can answer). /readyz is usable readiness (startup plugin sidecars, channels, and ready-critical post-attach work have settled). Startup hooks dispatch asynchronously and are not part of the readiness guarantee. Ready log time is the Gateway’s internal timestamp, useful for process-side attribution but not a substitute for the external /readyz probe.Use JSON output or --output when comparing changes. Use --cpu-prof-dir only after trace output points at import, compile, or CPU-bound work that phase timings alone cannot explain.Gateway restart (scripts/bench-gateway-restart.ts)
Gateway restart (scripts/bench-gateway-restart.ts)
macOS and Linux only (uses SIGUSR1 for in-process restarts; fails immediately on Windows). Same built-entry default and Case ids:
--entry scripts/run-node.mjs override as gateway startup above.skipChannels, skipChannelsAcpxProbe (ACPX startup probe on), skipChannelsNoAcpxProbe (probe off), default, fiftyPlugins.Output includes next /healthz, next /readyz, downtime, restart ready timing, CPU, RSS, startup trace metrics for the replacement process, and restart trace metrics for signal handling, active-work drain, close phases, next start, ready timing, and memory snapshots. The script sets OPENCLAW_GATEWAY_STARTUP_TRACE=1 and OPENCLAW_GATEWAY_RESTART_TRACE=1.Use this benchmark when a change touches restart signaling, close handlers, startup-after-restart, sidecar shutdown, service handoff, or readiness after restart. Start with skipChannels to isolate Gateway mechanics from channel startup; use default or plugin-heavy cases only after the narrow case explains the restart path. Trace metrics are attribution hints, not verdicts — judge a restart change from multiple samples, the matching owner span, /healthz//readyz behavior, and the user-visible restart contract.Onboarding E2E (Docker)
Optional; only needed for containerized onboarding smoke tests. Full cold-start flow in a clean Linux container:openclaw health.