OpenClaw Test Performance
Use evidence first. The goal is real pnpm test speed/RSS improvement with
coverage intact, not runner tuning by guesswork.
Workflow
- Read the relevant local
AGENTS.md files before editing:
src/agents/AGENTS.md for agent/import hotspots.
src/channels/AGENTS.md and src/plugins/AGENTS.md for plugin/channel
laziness.
src/gateway/AGENTS.md for server lifecycle tests.
test/helpers/AGENTS.md and test/helpers/channels/AGENTS.md for shared
contract helpers.
src/infra/outbound/AGENTS.md for outbound/media/action tests.
- Establish a baseline before changing code:
- Prefer
pnpm test:perf:groups --full-suite --allow-failures --output <file>
for full-suite ranking.
- For a scoped hotspot use:
/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose
- For import-heavy suspicion add:
OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1.
- Separate wall/runner noise from real file cost:
- Compare Vitest duration, test body timing, import breakdown, wall time, and
max RSS.
- Re-run single files when grouped/full-suite numbers look stale or noisy.
- If a full-suite grouped run reports a lane failure but JSON says tests
passed, capture that as harness/noise and verify the suspect file directly.
- Pick the next attack by return and risk:
- High return: one file/test dominates seconds or RSS and has a clear root.
- Lower risk: static descriptors, target parsing, routing, auth bypass,
setup hints, registry fixtures, or test server lifecycle.
- Higher risk: real memory/runtime behavior, live providers, protocol
contracts, or broad production refactors.
- Fix the root cause, not the symptom:
- Move static metadata/parsing into narrow helpers or lightweight artifacts
reused by full runtime and fast paths.
- Prefer dependency injection, loaded-plugin-only lookup, explicit fixtures,
and pure helpers over broad mocks.
- Reuse suite-level servers/clients when a fresh handshake is irrelevant.
- Keep schedulers/background loops off unless the test proves scheduling.
- Preserve coverage shape:
- Do not delete a slow integration proof unless the exact production
composition is extracted into a named helper and tested.
- Keep one cheap integration smoke when cross-component wiring matters.
- State explicitly what incidental coverage was removed, if any.
- Re-benchmark the same command after the change and compute seconds plus
percent gain.
- Update the running report when requested or when this thread is tracking one.
Include before/after commands, artifacts, coverage notes, verification, and
next attack order.
- Commit with
scripts/committer "<message>" <paths...> and push when the
user asked for commits/pushes. Stage only files touched for this attack.
Common Root Causes
- Full bundled channel/plugin runtime loaded for static data.
getChannelPlugin() fallback used when an already-loaded fixture or pure
parser would suffice.
- Broad
api.ts, runtime-api.ts, test-api.ts, or plugin-sdk barrels pulled
into hot tests.
- Partial-real mocks using
importActual() around broad modules.
vi.resetModules() plus fresh imports in per-test loops.
- Test plugin registry seeded in
beforeAll while runtime state resets in
afterEach.
- Per-test gateway/server/client startup when state reset would suffice.
- Runtime/default model/auth selection paid by idle snapshots or fixtures.
- Plugin-owned media/action discovery triggered before checking whether args
contain plugin-owned fields.
Benchmark Commands
Scoped file:
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose
Scoped file with import breakdown:
timeout 240 /usr/bin/time -l env \
OPENCLAW_VITEST_IMPORT_DURATIONS=1 \
OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 \
pnpm test <file> --maxWorkers=1 --reporter=verbose
Grouped suite:
pnpm test:perf:groups --full-suite --allow-failures \
--output .artifacts/test-perf/<name>.json
Reuse an existing Vitest JSON report:
pnpm test:perf:groups --report <vitest-json> \
--output .artifacts/test-perf/<name>.json
Verification
- Always run the targeted test surface that proves the change.
- Run
pnpm check before commit unless the change is docs-only and the hook
handles it.
- Run
pnpm build when touching lazy-loading, bundled artifacts, package
boundaries, dynamic imports, build output, or public surfaces.
- If deps are missing/stale, run
pnpm install and retry the exact failed
command once.
- Use the report format:
| Metric | Before | After | Gain |
| -------------- | -----: | ----: | ------------: |
| File wall time | `Xs` | `Ys` | `-Zs` (`P%`) |
| Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |
Handoff
Keep the final concise:
- Root cause.
- Files changed.
- Before/after numbers.
- Coverage retained.
- Verification commands.
- Commit hash and push status.
1---2name: openclaw-test-performance3description: Benchmark, diagnose, and optimize OpenClaw test performance without losing coverage. Use when Codex needs to reassess `pnpm test`, compare grouped Vitest reports, identify CPU/memory/import hotspots, fix slow tests or cold runtime paths, preserve behavior proofs, update the performance report, add AGENTS guardrails, and make scoped commits/pushes for OpenClaw test-speed work.4---56# OpenClaw Test Performance78Use evidence first. The goal is real `pnpm test` speed/RSS improvement with9coverage intact, not runner tuning by guesswork.1011## Workflow12131. Read the relevant local `AGENTS.md` files before editing:14 - `src/agents/AGENTS.md` for agent/import hotspots.15 - `src/channels/AGENTS.md` and `src/plugins/AGENTS.md` for plugin/channel16 laziness.17 - `src/gateway/AGENTS.md` for server lifecycle tests.18 - `test/helpers/AGENTS.md` and `test/helpers/channels/AGENTS.md` for shared19 contract helpers.20 - `src/infra/outbound/AGENTS.md` for outbound/media/action tests.212. Establish a baseline before changing code:22 - Prefer `pnpm test:perf:groups --full-suite --allow-failures --output <file>`23 for full-suite ranking.24 - For a scoped hotspot use:25 `/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose`26 - For import-heavy suspicion add:27 `OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1`.283. Separate wall/runner noise from real file cost:29 - Compare Vitest duration, test body timing, import breakdown, wall time, and30 max RSS.31 - Re-run single files when grouped/full-suite numbers look stale or noisy.32 - If a full-suite grouped run reports a lane failure but JSON says tests33 passed, capture that as harness/noise and verify the suspect file directly.344. Pick the next attack by return and risk:35 - High return: one file/test dominates seconds or RSS and has a clear root.36 - Lower risk: static descriptors, target parsing, routing, auth bypass,37 setup hints, registry fixtures, or test server lifecycle.38 - Higher risk: real memory/runtime behavior, live providers, protocol39 contracts, or broad production refactors.405. Fix the root cause, not the symptom:41 - Move static metadata/parsing into narrow helpers or lightweight artifacts42 reused by full runtime and fast paths.43 - Prefer dependency injection, loaded-plugin-only lookup, explicit fixtures,44 and pure helpers over broad mocks.45 - Reuse suite-level servers/clients when a fresh handshake is irrelevant.46 - Keep schedulers/background loops off unless the test proves scheduling.476. Preserve coverage shape:48 - Do not delete a slow integration proof unless the exact production49 composition is extracted into a named helper and tested.50 - Keep one cheap integration smoke when cross-component wiring matters.51 - State explicitly what incidental coverage was removed, if any.527. Re-benchmark the same command after the change and compute seconds plus53 percent gain.548. Update the running report when requested or when this thread is tracking one.55 Include before/after commands, artifacts, coverage notes, verification, and56 next attack order.579. Commit with `scripts/committer "<message>" <paths...>` and push when the58 user asked for commits/pushes. Stage only files touched for this attack.5960## Common Root Causes6162- Full bundled channel/plugin runtime loaded for static data.63- `getChannelPlugin()` fallback used when an already-loaded fixture or pure64 parser would suffice.65- Broad `api.ts`, `runtime-api.ts`, `test-api.ts`, or plugin-sdk barrels pulled66 into hot tests.67- Partial-real mocks using `importActual()` around broad modules.68- `vi.resetModules()` plus fresh imports in per-test loops.69- Test plugin registry seeded in `beforeAll` while runtime state resets in70 `afterEach`.71- Per-test gateway/server/client startup when state reset would suffice.72- Runtime/default model/auth selection paid by idle snapshots or fixtures.73- Plugin-owned media/action discovery triggered before checking whether args74 contain plugin-owned fields.7576## Benchmark Commands7778Scoped file:7980```bash81timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose82```8384Scoped file with import breakdown:8586```bash87timeout 240 /usr/bin/time -l env \88 OPENCLAW_VITEST_IMPORT_DURATIONS=1 \89 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 \90 pnpm test <file> --maxWorkers=1 --reporter=verbose91```9293Grouped suite:9495```bash96pnpm test:perf:groups --full-suite --allow-failures \97 --output .artifacts/test-perf/<name>.json98```99100Reuse an existing Vitest JSON report:101102```bash103pnpm test:perf:groups --report <vitest-json> \104 --output .artifacts/test-perf/<name>.json105```106107## Verification108109- Always run the targeted test surface that proves the change.110- Run `pnpm check` before commit unless the change is docs-only and the hook111 handles it.112- Run `pnpm build` when touching lazy-loading, bundled artifacts, package113 boundaries, dynamic imports, build output, or public surfaces.114- If deps are missing/stale, run `pnpm install` and retry the exact failed115 command once.116- Use the report format:117118```markdown119| Metric | Before | After | Gain |120| -------------- | -----: | ----: | ------------: |121| File wall time | `Xs` | `Ys` | `-Zs` (`P%`) |122| Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |123```124125## Handoff126127Keep the final concise:128129- Root cause.130- Files changed.131- Before/after numbers.132- Coverage retained.133- Verification commands.134- Commit hash and push status.