Repository navigation
Description
Goal
Migrate every direct legacy bash E2E entry point under test/e2e/test-*.sh into the typed Vitest E2E system, then delete or retire the legacy shell entry points as cleanup.
The end state is:
- E2E tests are TypeScript/Vitest tests.
- Real system boundaries are still tested when they are the contract: Vitest may invoke
bash,bash -lc, installer scripts, host commands,sudo-gated commands, process signals,/procprobes, Docker/OpenShell commands, and sandbox exec. - Shared setup, clients, shell probes, process supervision, artifacts, cleanup, and redaction live in Vitest fixtures/support code.
- Legacy bash scripts are not kept as a second durable E2E suite.
Core Principle
Do not classify tests as "keep bash" just because they need shell behavior.
If a contract needs shell, process, host, platform, installer, or sandbox behavior, preserve that boundary from a typed Vitest test:
- installer fidelity: run
bash install.sh ...from Vitest; - shell profile behavior: run
bash -lcor explicitly source profile files from Vitest; /procand kernel/process probes: run the probe in the same host or sandbox context from Vitest;- signal handling: use Node process control or host shell commands from Vitest;
- privileged/platform checks: use guarded host exec from Vitest and skip with evidence when unavailable;
- sandbox context: use typed sandbox clients /
openshell sandbox execwrappers from Vitest; - full user journeys: keep them as full-flow Vitest tests instead of splitting away their integration value.
Definition of Done for a Conversion
A legacy script is considered converted when all of the following are true:
- Equivalent typed Vitest coverage exists under the E2E Vitest tree or an appropriate focused Vitest test.
- The new test preserves the same user-visible contract, including shell/system boundaries where those boundaries matter.
- The test has deterministic artifacts, cleanup, timeout behavior, and secret redaction.
- The PR explains the contract mapping from the legacy shell script to the new Vitest coverage.
- The replacement test is wired into
.github/workflows/e2e-vitest-scenarios.yamlin the conversion PR so it can be dispatched on the same runner class as the legacy lane. - Any legacy shell workflow references that must be removed are removed, replaced, or explicitly deferred to Phase 11 in the same PR.
Shell Deletion Deferral and Governance
Default conversion PRs should defer legacy shell script deletion unless the PR is explicitly scoped to retire the script and its workflow lane. A script not being deleted is not by itself a reason to leave the conversion unchecked.
Deletion/quarantine of converted shell scripts is intentionally deferred to Phase 11 cleanup so parallel conversion PRs can land with less workflow and allowlist churn. During conversion phases:
- do not add new legacy
test/e2e/test-*.shentry points; - do not expand or casually rewrite existing legacy shell scripts;
- do not add new nightly/regression workflow wiring that runs legacy shell scripts unless maintainers intentionally approve it;
- every conversion PR must add the replacement Vitest execution path to
.github/workflows/e2e-vitest-scenarios.yamlbefore it is considered converted; - free-standing live/process Vitest replacements must get a discrete dispatchable job in
e2e-vitest-scenarios.yamlunless they are registered as a typed live scenario in the existing matrix; - do not rely on
e2e-scenarios-all/ emptyscenariosdispatch as proof for a newly added free-standing test unless the PR also wires that job intoe2e-vitest-scenarios.yaml; - move legacy shell workflow lanes to Vitest only when a conversion PR intentionally replaces that execution path; otherwise leave stable shell wiring for Phase 11 cleanup.
Repository governance exists to enforce this freeze:
test/e2e-script-workflow.test.tsfreezes the top-level legacy shell script allowlist;- the same test freezes scheduled nightly legacy shell wiring and verifies referenced files still exist;
- the test is included in the
cliVitest project and runs through PRcli-test-shards/ maincli-test-shards.
If a PR intentionally changes the legacy shell set or nightly shell wiring, it must update the allowlist/contract test with an explicit migration rationale.
Parallelization Model
The remaining work should be run as parallel tracks, not a single linear phase ladder.
- Phase 1 anchor PRs can start in parallel across tracks.
- Within a track, dependent migrations should wait until that track's anchor PR has established the helper/test shape, but they do not need to wait for anchors in other tracks.
- Prefer one owner per anchor to avoid competing helper designs.
- Avoid broad shared-fixture or workflow refactors inside individual conversions.
- Use local helpers first. Promote helpers to shared fixtures only when 3+ migrations clearly need the same boundary.
- Minimize workflow YAML contention, but do not skip required Vitest wiring: each migration still needs its own smallest safe
e2e-vitest-scenarios.yamldispatch path. - Use unique sandbox names, ports, artifact directories, and workflow job names in parallel PRs.
Phase Tracker
Legend: checked means equivalent Vitest conversion coverage has landed. Line counts are sizing hints from the legacy script inventory.
Phase 0 — Converted / already covered
-
test-onboard-inference-smoke.sh(163) → compact onboard + inference smoke — PR test(e2e): retire onboard inference smoke script #5155 merged -
test-docker-unreachable-gateway-start.sh(162) → gateway start failure/preflight Vitest regression — PR test(e2e): retire docker-unreachable gateway script #5119 merged (coverage seeded by PR test(onboard): add helper-level Vitest coverage for docker-unreachable gateway-start abort (#4355) #5109) -
test-dashboard-remote-bind.sh(72) → dashboard bind/remote accessibility guard — PR test(e2e): migrate dashboard remote bind #5186 merged -
test-whatsapp-qr-compact-e2e.sh(189) → WhatsApp QR compact behavior — PR test(e2e): migrate WhatsApp QR compact guard #5187 merged -
test-strict-tool-call-probe.sh(377) → strict tool-call probe — PR test(e2e): retire test-strict-tool-call-probe.sh #5153 merged -
test-vm-driver-privileged-exec-routing.sh(142) → privileged exec routing guard — PR test(e2e): migrate VM driver privexec coverage #5189 merged -
test-docs-validation.sh(163) → docs validation E2E — PR test(e2e): add Vitest docs validation coverage #5185 merged
Phase 1 — Parallel anchor PRs
Start these in parallel. Each anchor should establish the smallest useful local/helper shape for its track, then dependent scripts in that track can fan out.
-
test-double-onboard.sh(972) → onboarding lifecycle anchor: repeated onboarding, idempotency, repair prompts — PR test(e2e): migrate double onboard to Vitest [ANCHOR-1] #5218 merged -
test-sandbox-operations.sh(884) → sandbox/gateway anchor: create/list/status/connect/destroy operations — PR test(e2e): migrate test-sandbox-operations.sh to vitest [ANCHOR-2] #5224 merged -
test-inference-routing.sh(715) → inference/provider anchor: routing and error behavior — PR test(e2e): add inference routing Vitest coverage [ANCHOR-3] #5231 merged -
test-token-rotation.sh(603) → messaging/channel anchor: token lifecycle and channel secret refresh behavior — PR test(e2e): migrate test-token-rotation.sh to vitest [ANCHOR-4] #5236 merged -
test-hermes-e2e.sh(762) → Hermes anchor: install/onboard/runtime full flow — PR test(e2e): add Hermes live Vitest migration [ANCHOR-5] #5256 merged -
test-network-policy.sh(1133) → security/policy anchor: network policy enforcement and allow/deny probes — PR test(e2e): migrate network policy to Vitest [ANCHOR-6] #5226 merged -
test-rebuild-openclaw.sh(541) → rebuild/state anchor: OpenClaw rebuild flow and state preservation — PR test(e2e): migrate test-rebuild-openclaw.sh to vitest [ANCHOR-7] #5223 merged -
test-launchable-smoke.sh(593) → platform/manual anchor: launchable image smoke path — PR test(e2e): migrate test-launchable-smoke.sh to vitest [ANCHOR-8] #5219 merged
Phase 2 — Independent quick wins / no anchor dependency
These should not need a large shared helper or anchor PR. Convert opportunistically in parallel.
-
test-openshell-version-pin.sh(288) → installer-script Vitest test for OpenShell pinning — PR test(e2e): migrate test-openshell-version-pin.sh to free-standing Vitest live test #5107 merged -
test-model-router-provider-routed-inference.sh(196) → model-router provider-routed inference — PR test(e2e): migrate test-model-router-provider-routed-inference.sh to vitest [IND-2] #5221 merged -
test-issue-4434-tui-unreachable-inference.sh(197) → unreachable inference handling — PR test(e2e): migrate test-issue-4434-tui-unreachable-inference.sh to vitest #5233 merged -
test-hermes-root-entrypoint-smoke.sh(202) → Hermes root entrypoint smoke — PR test(e2e): migrate test-hermes-root-entrypoint-smoke.sh to vitest [IND-4] #5220 merged -
test-credential-migration.sh(302) → credential migration behavior — PR test(e2e): migrate credential migration to Vitest [IND-5] #5228 merged -
test-openclaw-plugin-runtime-exdev.sh(209) → plugin runtime EXDEV behavior — PR test(e2e): migrate openclaw plugin EXDEV guard [IND-6] #5232 merged -
test-runtime-overrides.sh(337) → runtime override behavior — PR test(e2e): migrate test-runtime-overrides.sh to vitest #5229 merged -
test-openclaw-tui-chat-correlation.sh(63) → TUI chat correlation smoke — PR test(e2e): add OpenClaw TUI chat-correlation coverage slice #5150 merged -
test-skill-agent-e2e.sh(268) → skill-agent E2E — PR test(e2e): migrate test-skill-agent-e2e.sh to vitest #5222 merged
Phase 3 — Onboarding and baseline dependents
Start after the onboarding anchor shape exists. These may share onboard/run/resume/repair helpers but can be separate PRs.
-
test-full-e2e.sh(510) → full Vitest user journey: install → onboard → inference → CLI ops → cleanup — PR test(e2e): migrate full e2e journey to Vitest #5493 merged -
test-cloud-onboard-e2e.sh(338) → public/cloud onboarding Vitest flow — PR test(e2e): migrate cloud onboard flow to Vitest #5494 merged -
test-cloud-inference-e2e.sh(291) → cloud inference Vitest flow — PR test(e2e): migrate cloud inference scenario #5361 merged -
test-common-egress-agent-e2e.sh(452) → common egress agent onboarding/runtime flow — PR test(e2e): migrate common-egress agent scenario #5360 merged -
test-gpu-double-onboard.sh(579) → GPU repeated onboarding variant — PR test(e2e): migrate GPU double onboard to Vitest #5495 merged -
test-onboard-repair.sh(400) → repair flow — PR test(e2e): migrate onboard repair flow to Vitest #5496 merged -
test-onboard-resume.sh(350) → interrupted/resumed onboarding — PR test(e2e): close out onboard resume Vitest migration #5497 merged -
test-onboard-negative-paths.sh(521) → invalid input and failure handling — PR test(e2e): bridge onboard negative paths to Vitest #5152 merged -
test-issue-4462-scope-upgrade-approval.sh(1058) → scope upgrade approval and no-leak process checks — PR test(e2e): migrate scope upgrade approval to Vitest #5498 merged
Phase 4 — Sandbox and gateway dependents
Start after the sandbox/gateway anchor shape exists. These can run in parallel with inference, messaging, Hermes, security, rebuild, and platform tracks.
-
test-sandbox-survival.sh(795) → lifecycle survival/restart behavior — PR test(e2e): migrate test-sandbox-survival.sh to vitest #5332 merged -
test-snapshot-commands.sh(288) → snapshot command behavior — PR test(e2e): migrate test-snapshot-commands.sh to vitest #5346 merged -
test-diagnostics.sh(513) → diagnostics collection and expected output — PR test(e2e): migrate test-diagnostics.sh to vitest #5347 merged -
test-issue-2478-crash-loop-recovery.sh(636) → gateway/sandbox crash-loop recovery — PR test(e2e): migrate test-issue-2478-crash-loop-recovery.sh to vitest #5348 merged -
test-concurrent-gateway-ports.sh(370) → concurrent gateway port allocation — PR test(e2e): migrate test-concurrent-gateway-ports.sh to vitest #5349 merged -
test-gateway-drift-preflight.sh(423) → gateway drift preflight detection — PR test(e2e): migrate test-gateway-drift-preflight.sh to vitest #5350 merged -
test-gateway-health-honest.sh(234) → honest gateway health reporting — PR test(e2e): migrate test-gateway-health-honest.sh to vitest #5351 merged
Phase 5 — Inference and provider dependents
Start after the inference/provider anchor shape exists. Mock-provider and live-provider slices can be split across owners.
-
test-gpu-e2e.sh(693) → GPU/Ollama inference flow — PR test(e2e): migrate GPU Ollama flow to vitest #5556 merged -
test-ollama-auth-proxy-e2e.sh(568) → Ollama auth proxy flow — PR test(e2e): migrate Ollama auth proxy to vitest #5551 merged -
test-kimi-inference-compat.sh(800) → Kimi compatibility — PR test(e2e): migrate Kimi compatibility to vitest #5555 merged -
test-openclaw-inference-switch.sh(519) → OpenClaw inference provider switching — PR test(e2e): migrate OpenClaw inference switch scenario #5357 merged -
test-hermes-inference-switch.sh(615) → Hermes inference provider switching — PR test(e2e): migrate Hermes inference switch to vitest #5553 merged -
test-bedrock-runtime-compatible-anthropic.sh(1020) → Bedrock/Anthropic-compatible runtime — PR test(e2e): migrate Bedrock Runtime compatible Anthropic scenario #5356 merged -
test-brave-search-e2e.sh(438) → Brave search integration — PR test(e2e): migrate Brave search to vitest #5552 merged -
test-agent-turn-latency-e2e.sh(629) → agent turn latency/runtime smoke — PR test(e2e): migrate agent turn latency to vitest #5554 merged -
test-cron-preflight-inference-local-e2e.sh(366) → cron preflightinference.local/ env-proxy mode — PR test(e2e): migrate cron preflight to vitest #5550 merged
Phase 6 — Messaging, channels, and pairing dependents
Start after the messaging anchor shape exists. Avoid making test-messaging-providers.sh the first PR; split provider/channel slices where possible.
-
test-messaging-providers.sh(3204) → provider matrix for Telegram/Discord/Slack/WeChat/WhatsApp behavior — PR test(e2e): add messaging providers vitest coverage #5364 merged -
test-telegram-injection.sh(476) → Telegram injection guard — PR test(e2e): migrate Telegram injection to Vitest #5576 merged -
test-messaging-compatible-endpoint.sh(679) → compatible endpoint messaging path — PR test(e2e): migrate messaging compatible endpoint #5362 merged -
test-channels-add-remove.sh(619) → channel add/remove lifecycle — PR test(e2e): migrate channels add remove scenario #5355 merged -
test-channels-stop-start.sh(813) → channel stop/start lifecycle — PR test(e2e): migrate channels stop/start to Vitest #5580 merged -
test-openclaw-discord-pairing.sh(637) → OpenClaw Discord pairing — PR test(e2e): migrate OpenClaw Discord pairing to Vitest #5581 merged -
test-openclaw-slack-pairing.sh(860) → OpenClaw Slack pairing — PR test(e2e): migrate test-openclaw-slack-pairing.sh to vitest #5582 merged
Phase 7 — Hermes dependents
Start after the Hermes anchor shape exists. Keep Hermes-specific helpers separate from OpenClaw helpers unless repeated boundaries prove otherwise.
-
test-hermes-sandbox-secret-boundary.sh(416) → Hermes sandbox secret boundary — PR test(e2e): migrate test-hermes-sandbox-secret-boundary.sh to vitest #5607 merged -
test-hermes-slack-e2e.sh(663) → Hermes Slack integration — PR test(e2e): migrate test-hermes-slack-e2e.sh to vitest #5605 merged -
test-hermes-discord-e2e.sh(656) → Hermes Discord integration — PR test(e2e): migrate test-hermes-discord-e2e.sh to vitest #5610 merged
Phase 8 — Security and policy dependents
Start after the security/policy anchor shape exists, except the independent quick wins already listed in Phase 2.
-
test-shields-config.sh(671) → shields configuration policy — PR test(e2e): migrate test-shields-config.sh to vitest #5337 merged -
test-credential-sanitization.sh(816) → credential sanitization and leak checks — PR test(e2e): migrate test-credential-sanitization.sh to vitest #5336 merged
Phase 9 — Rebuild, state, and runtime dependents
Start after the rebuild/state anchor shape exists. Runtime-only scripts that stay local can proceed independently if they do not need rebuild helpers.
-
test-rebuild-hermes.sh(406) → Hermes rebuild flow — PR test(e2e): migrate test-rebuild-hermes.sh to vitest #5588 merged -
test-upgrade-stale-sandbox.sh(241) → stale sandbox upgrade detection/recovery — PR test(e2e): migrate upgrade stale sandbox to vitest #5542 merged -
test-sandbox-rebuild.sh(197) → sandbox rebuild smoke or fold into rebuild coverage — PR test(e2e): migrate test-sandbox-rebuild.sh to vitest #5333 merged -
test-openshell-gateway-upgrade.sh(793) → OpenShell gateway upgrade behavior — PR test(e2e): migrate test-openshell-gateway-upgrade.sh to vitest #5609 merged -
test-state-backup-restore.sh(379) → state backup/restore lifecycle — PR test(e2e): migrate state backup restore to vitest #5353 merged -
test-overlayfs-autofix.sh(549) → overlayfs autofix behavior — PR test(e2e): migrate test-overlayfs-autofix.sh to vitest #5611 merged -
test-device-auth-health.sh(375) → device auth health flow — PR test(e2e): migrate device auth health to vitest #5543 merged -
test-tunnel-lifecycle.sh(516) → tunnel lifecycle — PR test(e2e): migrate test-tunnel-lifecycle.sh to vitest #5587 merged -
test-openclaw-skill-cli-e2e.sh(340) → OpenClaw skill CLI E2E — PR test(e2e): migrate OpenClaw skill CLI scenario #5354 merged -
test-sessions-agents-cli.sh(501) → sessions/agents CLI E2E — PR test(e2e): migrate sessions agents cli scenario #5363 merged
Phase 10 — Platform and resource-constrained dependents
These are valid parallel work, but they need explicit runner/resource planning and should not block non-platform tracks.
-
test-spark-install.sh(157) → Spark install path — PR test(e2e): migrate test-spark-install.sh to vitest #5608 merged -
test-jetson-nvmap-gpu.sh(323) → Jetson nvmap/GPU platform path — PR test(e2e): migrate test-jetson-nvmap-gpu.sh to vitest #5613 merged
Phase 11 — Review, refactor, and shell retirement cleanup
Phase 11 cleanup is being handled by PR #5756. It retires the legacy shell workflow/action lanes, removes converted top-level
test/e2e/test-*.shentrypoints, consolidates the surviving E2E target/live surface, promotes the repeated shell quoting helper, and adds retired-entrypoint workflow boundary tests. Check these boxes after #5756 lands.
Run this after enough anchor/dependent migrations have landed to see real duplication patterns.
- Review all migrated tests for repeated local helpers used by 3+ migrations.
- Promote repeated helper patterns into shared Vitest fixture/support locations with focused tests.
- Remove redundant local helper copies after shared helpers exist.
- Delete or quarantine converted legacy shell scripts that remain present only for transition.
- Remove stale shell workflow wiring and update existing workflow/allowlist contract tests.
- Confirm no durable legacy bash suite remains beyond intentionally documented platform/manual exceptions.
Tracking Guidance
Use this issue for migration order, parallelization guidance, and track ownership. Use individual PRs for exact assertion mapping, validation evidence, e2e-vitest-scenarios.yaml wiring, same-runner dispatch evidence, and script deletion/retirement notes. When a PR lands equivalent Vitest coverage with a dispatchable Vitest workflow path, link it here and mark the item complete even if deletion is deferred to Phase 11 cleanup.
Activity
Initial cutover plan placeholder
Initial cutover plan placeholder
ci(e2e): route scenario advisor to Vitest workflow (#5105)
test(e2e): typed-shell-runner cutover (parity → retirement) (#5106)
test(e2e): retire docker-unreachable gateway script (#5119)
test(e2e): add SandboxClient.get and SandboxClient.exists helpers
ci(e2e): add onboard-resume-vitest job to e2e-vitest-scenarios
test(e2e): add typed live test for onboard-resume regression (#446)
test(e2e): add SandboxClient.upload + execShell for live test fixtures
test(e2e): add openclaw-tui-chat-correlation free-standing live test …
test(e2e): pass framework env to onboard-resume spawns to fix ENOENT
test(e2e): pass framework env to sandbox.exists() in onboard-resume
fix(e2e): SandboxClient.exec must use `-n <name>` flag form, not posi…
test(e2e): mark test-onboard-resume.sh as covered after typed migration
119 remaining items
test(e2e): restore messaging bash parity (#5806)
test(e2e): close platform and workflow parity decisions (#5823)
cv commentedon Jun 28, 2026
Phase 11 cutover acceptance is now tracked in #5919, linked as a sub-issue of this epic.
#5919 is the only cutover tracker. It operates at old workflow-job variant granularity, requires reviewed Equivalent/Subsumed/Moved/Retired dispositions plus paired run evidence, and explicitly forbids adding a migration ledger or parity tracker to the repository.
The implementation path is intentionally incremental: missing Vitest variants, scheduled observability, and current-state ratchets land as small PRs on
mainwhile the old nightly still exists. #5756 then merges those changes and remains the terminal legacy deletion/rename PR. This epic and #5919 close together only after the final cutover and burn-in gates pass.test(e2e): retire legacy shell lanes (#5756)
jyaunches commentedon Jul 9, 2026
Closing as completed: both sub-issues (#5331 and #5919) are closed, and #5919 was the terminal cutover tracker for the legacy-to-Vitest E2E migration.
test(e2e): restore onboard rebuild lifecycle parity
test(e2e): restore inference helper parity (NVIDIA#5805)
test(e2e): restore recovery kimi scope parity (NVIDIA#5808)
test(e2e): restore messaging bash parity (NVIDIA#5807)
test(e2e): restore messaging bash parity (NVIDIA#5806)
test(e2e): close platform and workflow parity decisions (NVIDIA#5823)
test(e2e): retire legacy shell lanes (NVIDIA#5756)