e2e on exe.dev
For each unexpected failure, preserve the test evidence, then follow the companion
e2e-triage at a safe checkpoint and include its findings
and next actions in the final E2E report. Research each distinct failure once,
including when another E2E skill delegates here; do not delay active prompts,
change acceptance results or submit public issues/comments without authorization.
Install e2e-triage alongside this skill. If its relative link is unavailable,
resolve it by skill name in the agent's installed catalog; if absent, report that
triage was unavailable and continue the authorized test/report without installing
anything implicitly.
For an installation on an existing Mac, locally or over SSH, use
e2e-macos. This skill's target workflow is Linux;
its installer is shared with the Mac driver and its LaunchAgent helper.
For the real public interactive installer, use the companion
e2e-wizard and this driver's --interactive mode.
The workflow below remains the headless test; its setup repairs and acceptance
results do not qualify the wizard.
Installs NanoClaw on a disposable exe.dev VM without a human at the keyboard
and proves the whole loop: message in over the cli channel → host → container
→ Claude → reply out. It reuses the setup wizard's own step processes
(pnpm exec tsx setup/index.ts --step …) in the wizard's order — no parallel
install logic to drift — and replaces each prompt with an env var or a
pre-seeded state. The assertion is the wizard's first-chat probe
(setup/lib/agent-ping.ts): pnpm run chat ping must return a reply.
Ships in nanocoai/nanoclaw-oss-dev-tools
as skills/e2e-exe-dev, in the portable Agent Skills
format. Install it into any agent with npx skills add nanocoai/nanoclaw-oss-dev-tools,
or in Claude Code with /plugin install nanoclaw-e2e@nanoclaw-oss-dev-tools (there it
is invoked as /nanoclaw-e2e:e2e-exe-dev). In Codex, invoke $e2e-exe-dev.
Resolve the scripts relative to this SKILL.md, then run them from the root
of the NanoClaw checkout you want tested. The installed skill and the
checkout being tested are separate directories. Install e2e-wizard alongside
this skill; its shared read-only helper discovers the exact provider picker.
Three scripts under scripts/:
| Script |
Runs on |
Does |
exe-run.sh |
your machine |
ssh exe.dev new --json (or cp from a base VM), pushes the selected credential and installer over SSH, clones the ref, runs the installer, exports evidence, optionally snapshots (--snapshot) or performs guarded deletion on pass (--rm) |
e2e-install.sh |
the VM (or any Debian/Ubuntu box, or a CI runner) |
the headless install + ping; exits 0 on pass |
e2e-evidence.py |
VM and operator machine |
creates, redacts and validates the headless evidence bundle before any requested teardown |
When to use
- A branch or PR touches setup, the container image, OneCLI wiring, the
service, or the router/delivery path and unit tests can't prove it.
- Someone reports an install failure you want to reproduce on a clean machine.
- You want a base VM with Docker, OneCLI and the built image already in place
so later runs skip the 3–10 minute build.
Not for: wiring real channels (Slack, Telegram, …). The e2e agent talks over
the always-on cli channel (src/channels/cli.ts) so no platform credentials
are involved. Use /manage-channels on the VM afterwards if you want more.
Prerequisites
An exe.dev account with ssh exe.dev working from your machine. exe.dev
has two SSH destinations: ssh exe.dev <cmd> is the lobby (VM
lifecycle only — no shell, no scp) and ssh <ssh_dest> is the VM
(full shell). Pin the key for both so non-interactive runs never stall on
key selection:
Host exe.dev *.exe.xyz
IdentitiesOnly yes
IdentityFile ~/.ssh/id_ed25519
exe.dev publishes its own agent skill (using-exe-dev, in
skill/SKILL.md of github.com/boldsoftware/exe.dev) and docs at
https://exe.dev/docs.md (index) / https://exe.dev/docs/all.md (one page).
ssh exe.dev help <command> is the authoritative flag reference; this
skill only adds the NanoClaw side.
For the headless Claude path, an Anthropic API key or OAuth token in a local file, default
~/.nanoclaw-e2e/anthropic_key. It goes to the VM over stdin into a
0600 file; the installer seeds it into the OneCLI vault exactly as
setup/auth.ts does (onecli secrets create --type anthropic --host-pattern api.anthropic.com).
A Debian/Ubuntu image on the VM (exe.dev's default). Both bootstrap
helpers are apt-based: setup/install-node.sh (NodeSource) and
setup/install-docker.sh (get.docker.com + usermod -aG docker).
Docker runs inside exe.dev VMs.
Choose the provider and authentication first
Resolve the exact NanoClaw commit and inspect its provider picker before reading
a credential or provisioning anything. The discovery helper belongs to the
sibling e2e-wizard skill and reads Git objects, so uncommitted files cannot
change the choices:
COMMIT="$(git rev-parse --verify HEAD^{commit})"
PROVIDER_HELPER=/absolute/path/to/installed/e2e-wizard/scripts/provider-options.py
python3 "$PROVIDER_HELPER" --root "$PWD" --revision "$COMMIT"
Show the reported providers to the operator and ask which provider to test.
Then inspect that provider and show its exact auth_prompt and auth_methods:
python3 "$PROVIDER_HELPER" --root "$PWD" --revision "$COMMIT" --provider claude
For an installable provider, first fetch the one nc:copy from-branch: payload
named by its offered skill from the owning remote, then pass that fetched ref as
--payload-ref. Record both nanoclaw_commit and auth_source_commit. Never
guess a payload remote or silently choose a provider or auth method.
The unattended headless installer currently supports Claude api and oauth.
Its existing mode is only for an explicitly selected reused gateway whose
vault already has a usable Anthropic credential; it is not a public-picker
choice. The public wizard can also automate credential-file methods exposed by
other offered providers. Browser, subscription, or device methods require a
separately authorized live human handoff; these drivers stop before allocation
because they cannot complete that handoff unattended. skip cannot produce an
E2E pass. Read a credential only after the operator chooses its matching method.
Workflow
Run it from the checkout root, on the branch you want tested. Set
E2E_SKILL_DIR to the actual directory containing this SKILL.md:
E2E_SKILL_DIR=/absolute/path/to/installed/e2e-exe-dev
cd /absolute/path/to/nanoclaw
bash "$E2E_SKILL_DIR/scripts/exe-run.sh" \
--provider claude --auth-method api --credential-file /path/to/anthropic-key \
--result-file /path/to/results/exe-headless.json
bash "$E2E_SKILL_DIR/scripts/exe-run.sh" --ref origin/main --name nc-main \
--provider claude --auth-method oauth --credential-file /path/to/anthropic-oauth-token \
--result-file /path/to/results/exe-main.json
Keep --result-file on every live run so the report and sanitized evidence
exist before the post-run retention choice. For an already authorized
disposable run, add --rm (the result parent directory must exist).
--rm is rejected without --result-file; <result-file>.artifacts must be
a new local path so teardown cannot precede validated evidence persistence.
--ref resolves in the local checkout to an exact commit before creating
a VM. Fetch locally first if you want an updated origin/main. The VM
fetches that SHA from --repo (default: local origin), checks it out
detached, and verifies HEAD matches. The commit must be fetchable from
that repository; local-only commits and uncommitted edits are not uploaded.
The installer comes from the installed skill, so compatible older NanoClaw
refs can be tested too.
Read the result. The last thing printed on success is
=== NANOCLAW E2E: RESULT ===
STATUS: pass
COMMIT: <full tested SHA>
SERVICE_TYPE: systemd-user | systemd-system | nohup
PING: ok
REPLY: <first 200 chars of the agent's reply>
RESULT: logs/e2e/result.json
=== END ===
Exit codes: 0 pass · 1 a step failed (its === NANOCLAW SETUP: … ===
block is printed right above the failure; raw output in
~/nanoclaw/logs/e2e/<step>.log on the VM) · 2 no reply / auth error ·
3 the host never opened data/cli.sock or the socket was unreachable.
The driver uses 64 for invalid arguments, 65 for checkout/ref errors,
66 for an unreadable key, 69 for unconfirmed VM creation/reachability,
70 for unconfirmed snapshot creation, and 74 for local result writing/export failure;
other command failures can return their own nonzero exit code.
After installer preflight, logs/e2e/result.json records pass/failure,
exit code, tested commit, tracked changes present at start, phase, service
type, ping classification and timestamps. An in-progress run has status
running. It replaces a previous run's result and contains no credentials
or reply text. With --result-file, after argument parsing and destination
validation the driver replaces any old local report with running before
checking the checkout, credentials or VM. It exports a matching completed
installer result and a sanitized evidence directory before snapshot/removal.
The evidence binds the run ID, NanoClaw SHA, provider, auth method, provider
auth-source SHA, dev-tools SHA when available, and exact harness digest. It
includes checksummed setup/runtime logs and a service/container/socket state
snapshot. Otherwise the driver records the driver
failure, exit code, phase and requested ref/commit; commit is null because
no tested revision was confirmed. An interrupted run may remain running.
A failed export keeps the VM. Once exported, the installer result describes
the test; snapshot/removal errors are reported by the driver's exit code.
On the VM, a checkout failure on a base leaves a running record with phase
checkout and the requested commit, replacing any old pass. Failures before
installer preflight on a fresh VM may have no remote result file; the local
driver report still records the failure.
Bake a base VM once the run is green, then clone it for every later run:
bash "$E2E_SKILL_DIR/scripts/exe-run.sh" --snapshot my-nanoclaw-e2e-base \
--provider claude --auth-method api --credential-file /path/to/anthropic-key \
--result-file /path/to/results/base.json
bash "$E2E_SKILL_DIR/scripts/exe-run.sh" --base my-nanoclaw-e2e-base --ref my-branch \
--provider claude --auth-method api --credential-file /path/to/anthropic-key \
--result-file /path/to/results/cached.json
VM names are global across exe.dev (they become <name>.exe.xyz), so
pick a distinctive snapshot name. Default run names include a random
suffix. A taken name or invalid create/copy response stops the run; an
existing VM is never adopted as a replacement. A failed snapshot keeps
the tested VM, even with --rm.
Every step is idempotent against the cloned state: onecli --reuse when
.env already has ONECLI_URL, auth --check short-circuits on an
existing vault secret, container rebuilds only what changed,
init-cli-agent reuses the group and wiring. Cached checkouts are pointed
at the requested repository and exact SHA; tracked edits in a base
checkout cause a failure instead of being overwritten.
Poke at it. ssh <ssh_dest> (printed at the end; ssh exe.dev ls --json lists it), then in ~/nanoclaw: pnpm run chat hi,
bin/ncl groups list, tail -f logs/nanoclaw.log.
Choose retention after evidence is safe. Recommend deleting a disposable,
run-owned VM after a pass and retaining an unexpected failure until triage is
complete. Offer the operator that choice after reporting the result. With
--rm, the driver revalidates its run marker, confirms no harness controller
is active, calls ssh exe.dev rm <name> --json, verifies the name is absent
from ssh exe.dev ls --json, and writes
teardown-receipt.json into the validated artifact directory. Any failed
export, checksum, redaction, local persistence, ownership, controller, or
absence check blocks or leaves teardown unconfirmed. For manual cleanup,
repeat the same checks and record the receipt; inventory lookup alone never
proves ownership.
Running the installer somewhere else
e2e-install.sh has no exe.dev dependency. On a CI runner or any fresh box:
git clone https://github.com/nanocoai/nanoclaw-oss-dev-tools.git
E2E_SKILL_DIR="$(pwd)/nanoclaw-oss-dev-tools/skills/e2e-exe-dev"
git clone https://github.com/nanocoai/nanoclaw.git
cd nanoclaw
NANOCLAW_E2E_PROVIDER=claude NANOCLAW_E2E_AUTH_METHOD=api \
NANOCLAW_E2E_AUTH_SOURCE_COMMIT="$(git rev-parse HEAD)" \
NANOCLAW_E2E_KEY_FILE=/path/to/key bash "$E2E_SKILL_DIR/scripts/e2e-install.sh"
The target needs git and python3 before starting the installer. The exe.dev
driver installs these if missing; provision them yourself on other hosts.
Env it honors: NANOCLAW_E2E_ROOT, NANOCLAW_E2E_KEY_FILE,
NANOCLAW_ONECLI_API_HOST + NANOCLAW_ONECLI_API_TOKEN (remote gateway
instead of a local install — the same vars setup/auto.ts reads),
NANOCLAW_DISPLAY_NAME, NANOCLAW_E2E_TZ (default UTC),
NANOCLAW_E2E_KEEP_AGENT (default 1), NANOCLAW_E2E_FORCE_AUTH (1
replaces an existing vault secret with the key file — token rotation on a
--base VM whose snapshot still holds the old one).
The driver forwards the remote gateway settings, display name, timezone,
keep-agent and force-auth settings over stdin into a private environment
file, which is removed after loading. NANOCLAW_E2E_KEY_FILE (or --key-file)
selects the local key to upload; NANOCLAW_E2E_ROOT applies to standalone
installer runs. The driver always uses ~/nanoclaw on its VM.
Compatibility evidence
On 2026-09-12 a fresh exe.dev VM failed at the onecli step against
NanoClaw fd767d381fe5a1fea51373e4e8cb411da79880ec with Port 5432 is already in use and nothing listening: the image had started shipping /exe.dev/bin/sh
first on PATH (see the gotcha below). With the installer's system-shell
preflight, a new fresh VM passed end to end against
d96dde93db7a766531ca288190ca202ad473547e:
the note line reported /exe.dev/bin removed from PATH, every step from
environment to verify succeeded, SERVICE_TYPE: nohup, PING: ok, exit 0,
about 3 minutes 20 seconds including OS bootstrap, and the VM was removed with
--rm. NanoClaw itself pins /bin/sh in those steps from
nanocoai/nanoclaw#3776, so
later refs no longer depend on this preflight.
Fresh and cached VM installations passed on 2026-09-10 against NanoClaw
74224f62a6c08418acccc727114ab02f92e403bf.
Both produced real model replies, SERVICE: running, and successful final
verification using the nohup fallback. The cached run reused OneCLI, the
vault credential, Docker build layers and the existing agent wiring.
The fresh install passed with skill version 0.2.3, but its subsequent
snapshot returned driver exit 70: exe.dev created the copy using a response
shape that the driver did not recognize. Version 0.2.4 accepts that shape
only when its source and destination names match the request. The corrected
driver's live cached-copy run completed with exit 0; offline regressions
also cover snapshot confirmation and mismatched copy responses.
Older NanoClaw refs, including 2c754a2234390fcc597273cef6344d99e8ac03d0,
could fail verification after a successful ping when systemd's user bus was
unavailable. The live-tested ref fixes this by consulting the nohup PID file
when no service is found. Check the selected ref before assuming compatibility.
What the installer calls, and why each call is shaped that way
Everything below is a wizard step or a script that already ships; the
installer adds sequencing and assertions only.
| # |
Call |
Source of truth |
| 1 |
bash setup.sh |
The launcher's prompt-free bootstrap (nanoclaw.sh runs it under a spinner): Node 22 via setup/install-node.sh, pnpm via corepack/npm, pnpm install --frozen-lockfile, native-module check |
| 2 |
bash setup/install-docker.sh, then sg docker re-exec if the socket is group-gated |
setup/container.ts does the same for its own step; done once up front because the onecli step (a docker-compose install) needs the daemon first |
| 3 |
--step environment |
Informational (setup/environment.ts never fails on a missing Docker) — kept for the log |
| 4 |
--step onecli | --reuse | --remote-url <host> |
setup/onecli.ts; mode chosen the way setup/auto.ts chooses it (NANOCLAW_ONECLI_API_HOST → remote; existing install → reuse; else fresh curl onecli.sh/install | sh + CLI from GitHub releases, pinned by versions.json) |
| 5 |
--step auth --check, then --create --value <key> only when STATUS: missing |
setup/auth.ts; mirrors runAuthStep's anthropicSecretExists() short-circuit |
| 6 |
--step container |
setup/container.ts: local docker build (or pull when .env has NANOCLAW_HARDENED_IMAGE=true), then the in-container smoke test |
| 7 |
--step mounts --empty |
The wizard's own args (setup/auto.ts); skipped on re-runs is fine |
| 8 |
--step timezone --tz <zone> |
setup/timezone.ts validates with isValidTimezone and writes TZ to .env |
| 9 |
--step service, then ./start-nanoclaw.sh iff SERVICE_TYPE: nohup |
setup/service.ts: builds, stamps the upgrade marker (so the host's tripwire passes), installs a system unit as root / user unit otherwise / nohup wrapper without user systemd |
| 10 |
Wait for data/cli.sock, then scripts/init-cli-agent.ts --display-name … --agent-name "E2E Agent" --folder e2e-agent |
src/index.ts finishes host migrations before opening the CLI socket. Only then run the initializer, which also migrates the DB, to create the cli:local scratch user, agent group and wiring without racing fresh host migrations. |
| 11 |
pnpm --silent run chat ping |
scripts/chat.ts: exit 0 + reply = ok, 2 = socket unreachable, 3 = no reply within its 120 s stop; the auth-error patterns are the ones agent-ping.ts classifies |
| 12 |
--step verify |
setup/verify.ts: success iff service running ∧ credentials present ∧ (groups > 0 ∨ wiring pending); exits 1 otherwise |
Step output is parsed from the === NANOCLAW SETUP: <STEP> === … STATUS: … === END ===
block every step prints (setup/status.ts).
Gotchas (each one cost a wrong assumption)
sh on PATH may not be the system shell. exe.dev images since
2026-09-09 put /exe.dev/bin first on PATH with their own sh, whose
builtin lsof always exits 0. NanoClaw's setup pipes downloaded installers
into sh (setup/onecli.ts, setup/install-docker.sh), so the OneCLI
installer's port probe reported every port as busy — Port 5432 is already in use (probably a local PostgreSQL) with nothing listening, and the same
for POSTGRES_PORT=5433. The installer now drops the directory holding a
foreign sh from PATH before the first step (it prints a note: line) and
stops when no system shell exists at /bin/sh or /usr/bin/sh. The
product-side fix pins /bin/sh in those steps.
- The wizard is not headless.
pnpm run setup:auto prompts for start
mode, existing-install action and OneCLI reuse with no env bypass, and
nanoclaw.sh reads its root warning from /dev/tty. Only the step runner
(setup/index.ts --step) is prompt-free — that's why the installer is
built on it and never touches nanoclaw.sh/setup:auto.
- No user systemd ⇒ nothing starts.
setupNohupFallback writes
start-nanoclaw.sh and reports SERVICE_LOADED: false; it does not run
it. The installer starts it. exe.dev VMs are exactly this shape: systemd
is PID 1 but systemctl --user fails ("Failed to connect to bus"), so
service falls back to nohup. verify.ts used to check nanoclaw.pid
only when there was no systemd at all and reported SERVICE: not_found
here. The live-tested SHA above fixes this by checking the PID file whenever
no service is found; older NanoClaw refs may still have the limitation.
- A non-interactive SSH shell has no
~/.local/bin on PATH — pnpm,
node, onecli all live there. Prefix ad-hoc commands on the VM with
export PATH="$HOME/.local/bin:$PATH"; the installer does this itself.
- Treat the token as exposed once it has been in any log. The installer
redacts
--value in its own output, but the OAuth token is long-lived:
rotate it (revoke, claude setup-token) after a run whose logs left the
machine, and re-seed base VMs with NANOCLAW_E2E_FORCE_AUTH=1.
- Keep the agent, or
verify fails. The wizard deletes its ping_test
group after the ping (scripts/delete-cli-agent.ts) before running
verify, which then relies on a channel deferring its wiring. With no
channel configured that's registeredGroups: 0 → failed. The installer
keeps e2e-agent (set NANOCLAW_E2E_KEEP_AGENT=0 to delete it after the
ping; expect verify to fail then).
- The vault is not optional. Spawn refuses without a gateway
contribution (
src/gateway-providers/onecli.ts: "refusing to spawn
container without credentials"), and onecli is the only provider in
trunk. There is no API-key-in-env shortcut.
- Docker is the only driver.
src/drivers/index.ts ships docker alone;
NANOCLAW_RUNTIME_DRIVER set to anything else throws at startup.
- First container boot is 30–60 s after the ping is sent;
chat.ts
waits up to 120 s and the installer wraps it in timeout 150.
GITHUB_TOKEN in the environment counts as a configured channel in
verify.ts (has() reads process.env). The installer unsets it so the
report describes the install, not the shell.
- Egress the VM must have:
deb.nodesource.com, get.docker.com,
onecli.sh, github.com (OneCLI CLI release tarball), Docker Hub (base
image), api.anthropic.com. exe.dev VMs have a full network stack; locked-
down runners do not.
- The key is on argv for one process.
auth --create --value is how
setup/auth.ts accepts it (it then execFileSyncs onecli, no shell), so
it is visible to ps on the VM for that step. Fine for a disposable VM;
use NANOCLAW_ONECLI_API_HOST with a pre-seeded remote vault if not.
- Don't synthesize the VM hostname.
new --json returns vm_name and
ssh_dest; the current cp --json response uses name, source and
ssh_host. The driver checks the requested destination name and, for that
copy format, the source name too. It also accepts the older vm_name
response shape. Modern destinations look like <name>.exe.xyz, legacy
ones like vm+<name>@vm.exe.xyz; the driver passes the returned destination
to SSH. It never falls back to ls --json to infer creation success.
- First contact blocks on the host-key prompt in a non-interactive
shell with nothing visible. Every VM connection in the driver carries
-o StrictHostKeyChecking=accept-new.
- The lobby is not a shell.
scp/sftp/commands against exe.dev
fail; files go to the VM destination, and the driver uses
ssh <vm> 'cat > file' < local (exe.dev's documented scp-less path).
- exe.dev
--setup-script is capped at 10 KiB and runs at first boot,
before you can hand it a secret safely. The driver therefore SSHes in after
boot instead — no size limit, secrets over stdin.
Troubleshooting
| Symptom |
Look at |
Likely cause |
setup.sh failed |
logs/e2e/bootstrap.log |
apt/NodeSource unreachable, or corepack enable needed sudo and none was passwordless |
docker socket still not accessible under sg docker |
id -nG, ls -l /var/run/docker.sock |
daemon not started; run sudo systemctl start docker and re-run |
onecli install failed / could_not_resolve_api_host |
logs/e2e/onecli.log, logs/setup.log |
onecli.sh or GitHub releases blocked; docker compose missing from the Docker install |
auth --check reported failed |
onecli secrets list on the VM |
gateway not healthy on port 10254 |
container step failed |
logs/e2e/container.log |
Docker Hub pull blocked, or the smoke test failed inside the image |
exit 3 (no data/cli.sock) |
logs/nanoclaw.error.log |
host crashed at start — read the first ERROR; the upgrade tripwire is not it (service stamped the marker) |
exit 2 with an auth-error reply |
the reply text in logs/e2e/ping.out |
wrong or expired key in the vault; onecli secrets to fix, then re-run (auth step short-circuits, so delete the bad secret first) |
exit 2, no reply at all |
logs/nanoclaw.log around the ping timestamp, bin/ncl sessions list |
container failed to spawn (OneCLI "not applied"), or the agent errored — container logs are gone after exit, so check the outbound DB: pnpm exec tsx scripts/q.ts data/v2-sessions/<group>/<session>/outbound.db "select * from messages_out" |
verify reported failed after a green ping |
the SERVICE: / CREDENTIALS: / REGISTERED_GROUPS: fields |
agent deleted (KEEP_AGENT=0); or SERVICE: not_found on a nohup-started host — see the source-review limitation above |
snapshot … creation was not confirmed / NAME creation was not confirmed |
the lobby response right above it |
name taken, malformed response, or copy source/destination mismatch — check before retrying |
Teardown
ssh exe.dev rm <name> when done — the VM's persistent disk holds the vault
secret. A standalone installer writes setup state and logs into its target
checkout; the driver runs setup on the VM. Uninstall the plugin
with /plugin uninstall nanoclaw-e2e@nanoclaw-oss-dev-tools.
1---2name: e2e-exe-dev3description: Provision an exe.dev VM and run a headless NanoClaw install on it end to end — Node/pnpm, Docker, OneCLI, vault secret, agent image, service, a cli-channel agent — then assert the same ping round-trip the setup wizard uses. Use to test a branch or PR on a real machine, to reproduce an install failure, or to bake a reusable base VM. Triggers on "e2e test on exe.dev", "install nanoclaw on a vm", "headless install", "test this branch end to end", "exe.dev".4license: MIT5---67# e2e on exe.dev89For each unexpected failure, preserve the test evidence, then follow the companion10[e2e-triage](../e2e-triage/SKILL.md) at a safe checkpoint and include its findings11and next actions in the final E2E report. Research each distinct failure once,12including when another E2E skill delegates here; do not delay active prompts,13change acceptance results or submit public issues/comments without authorization.14Install `e2e-triage` alongside this skill. If its relative link is unavailable,15resolve it by skill name in the agent's installed catalog; if absent, report that16triage was unavailable and continue the authorized test/report without installing17anything implicitly.1819For an installation on an existing Mac, locally or over SSH, use20[`e2e-macos`](../e2e-macos/SKILL.md). This skill's target workflow is Linux;21its installer is shared with the Mac driver and its LaunchAgent helper.2223For the real public interactive installer, use the companion24[e2e-wizard](../e2e-wizard/SKILL.md) and this driver's `--interactive` mode.25The workflow below remains the headless test; its setup repairs and acceptance26results do not qualify the wizard.2728Installs NanoClaw on a disposable exe.dev VM without a human at the keyboard29and proves the whole loop: message in over the cli channel → host → container30→ Claude → reply out. It reuses the setup wizard's own step processes31(`pnpm exec tsx setup/index.ts --step …`) in the wizard's order — no parallel32install logic to drift — and replaces each prompt with an env var or a33pre-seeded state. The assertion is the wizard's first-chat probe34(`setup/lib/agent-ping.ts`): `pnpm run chat ping` must return a reply.3536Ships in [nanocoai/nanoclaw-oss-dev-tools](https://github.com/nanocoai/nanoclaw-oss-dev-tools)37as `skills/e2e-exe-dev`, in the portable [Agent Skills](https://agentskills.io)38format. Install it into any agent with `npx skills add nanocoai/nanoclaw-oss-dev-tools`,39or in Claude Code with `/plugin install nanoclaw-e2e@nanoclaw-oss-dev-tools` (there it40is invoked as `/nanoclaw-e2e:e2e-exe-dev`). In Codex, invoke `$e2e-exe-dev`.41Resolve the scripts relative to this `SKILL.md`, then run them **from the root42of the NanoClaw checkout you want tested**. The installed skill and the43checkout being tested are separate directories. Install `e2e-wizard` alongside44this skill; its shared read-only helper discovers the exact provider picker.4546Three scripts under `scripts/`:4748| Script | Runs on | Does |49|---|---|---|50| `exe-run.sh` | your machine | `ssh exe.dev new --json` (or `cp` from a base VM), pushes the selected credential and installer over SSH, clones the ref, runs the installer, exports evidence, optionally snapshots (`--snapshot`) or performs guarded deletion on pass (`--rm`) |51| `e2e-install.sh` | the VM (or any Debian/Ubuntu box, or a CI runner) | the headless install + ping; exits 0 on pass |52| `e2e-evidence.py` | VM and operator machine | creates, redacts and validates the headless evidence bundle before any requested teardown |5354## When to use5556- A branch or PR touches setup, the container image, OneCLI wiring, the57 service, or the router/delivery path and unit tests can't prove it.58- Someone reports an install failure you want to reproduce on a clean machine.59- You want a base VM with Docker, OneCLI and the built image already in place60 so later runs skip the 3–10 minute build.6162Not for: wiring real channels (Slack, Telegram, …). The e2e agent talks over63the always-on `cli` channel (`src/channels/cli.ts`) so no platform credentials64are involved. Use `/manage-channels` on the VM afterwards if you want more.6566## Prerequisites6768- An exe.dev account with `ssh exe.dev` working from your machine. exe.dev69 has two SSH destinations: `ssh exe.dev <cmd>` is the **lobby** (VM70 lifecycle only — no shell, no scp) and `ssh <ssh_dest>` is the **VM**71 (full shell). Pin the key for both so non-interactive runs never stall on72 key selection:7374 ```75 Host exe.dev *.exe.xyz76 IdentitiesOnly yes77 IdentityFile ~/.ssh/id_ed2551978 ```7980 exe.dev publishes its own agent skill (`using-exe-dev`, in81 `skill/SKILL.md` of github.com/boldsoftware/exe.dev) and docs at82 https://exe.dev/docs.md (index) / https://exe.dev/docs/all.md (one page).83 `ssh exe.dev help <command>` is the authoritative flag reference; this84 skill only adds the NanoClaw side.85- For the headless Claude path, an Anthropic API key or OAuth token in a local file, default86 `~/.nanoclaw-e2e/anthropic_key`. It goes to the VM over stdin into a87 `0600` file; the installer seeds it into the OneCLI vault exactly as88 `setup/auth.ts` does (`onecli secrets create --type anthropic89 --host-pattern api.anthropic.com`).90- A Debian/Ubuntu image on the VM (exe.dev's default). Both bootstrap91 helpers are apt-based: `setup/install-node.sh` (NodeSource) and92 `setup/install-docker.sh` (`get.docker.com` + `usermod -aG docker`).93 Docker runs inside exe.dev VMs.9495## Choose the provider and authentication first9697Resolve the exact NanoClaw commit and inspect its provider picker before reading98a credential or provisioning anything. The discovery helper belongs to the99sibling `e2e-wizard` skill and reads Git objects, so uncommitted files cannot100change the choices:101102```bash103COMMIT="$(git rev-parse --verify HEAD^{commit})"104PROVIDER_HELPER=/absolute/path/to/installed/e2e-wizard/scripts/provider-options.py105python3 "$PROVIDER_HELPER" --root "$PWD" --revision "$COMMIT"106```107108Show the reported `providers` to the operator and ask which provider to test.109Then inspect that provider and show its exact `auth_prompt` and `auth_methods`:110111```bash112python3 "$PROVIDER_HELPER" --root "$PWD" --revision "$COMMIT" --provider claude113```114115For an installable provider, first fetch the one `nc:copy from-branch:` payload116named by its offered skill from the owning remote, then pass that fetched ref as117`--payload-ref`. Record both `nanoclaw_commit` and `auth_source_commit`. Never118guess a payload remote or silently choose a provider or auth method.119120The unattended headless installer currently supports Claude `api` and `oauth`.121Its `existing` mode is only for an explicitly selected reused gateway whose122vault already has a usable Anthropic credential; it is not a public-picker123choice. The public wizard can also automate credential-file methods exposed by124other offered providers. Browser, subscription, or device methods require a125separately authorized live human handoff; these drivers stop before allocation126because they cannot complete that handoff unattended. `skip` cannot produce an127E2E pass. Read a credential only after the operator chooses its matching method.128129## Workflow1301311. **Run it** from the checkout root, on the branch you want tested. Set132 `E2E_SKILL_DIR` to the actual directory containing this `SKILL.md`:133134 ```bash135 E2E_SKILL_DIR=/absolute/path/to/installed/e2e-exe-dev136 cd /absolute/path/to/nanoclaw137 bash "$E2E_SKILL_DIR/scripts/exe-run.sh" \138 --provider claude --auth-method api --credential-file /path/to/anthropic-key \139 --result-file /path/to/results/exe-headless.json140 bash "$E2E_SKILL_DIR/scripts/exe-run.sh" --ref origin/main --name nc-main \141 --provider claude --auth-method oauth --credential-file /path/to/anthropic-oauth-token \142 --result-file /path/to/results/exe-main.json143 ```144145 Keep `--result-file` on every live run so the report and sanitized evidence146 exist before the post-run retention choice. For an already authorized147 disposable run, add `--rm` (the result parent directory must exist).148 `--rm` is rejected without `--result-file`; `<result-file>.artifacts` must be149 a new local path so teardown cannot precede validated evidence persistence.150151 `--ref` resolves in the local checkout to an exact commit before creating152 a VM. Fetch locally first if you want an updated `origin/main`. The VM153 fetches that SHA from `--repo` (default: local `origin`), checks it out154 detached, and verifies `HEAD` matches. The commit must be fetchable from155 that repository; local-only commits and uncommitted edits are not uploaded.156 The installer comes from the installed skill, so compatible older NanoClaw157 refs can be tested too.1581592. **Read the result.** The last thing printed on success is160161 ```162 === NANOCLAW E2E: RESULT ===163 STATUS: pass164 COMMIT: <full tested SHA>165 SERVICE_TYPE: systemd-user | systemd-system | nohup166 PING: ok167 REPLY: <first 200 chars of the agent's reply>168 RESULT: logs/e2e/result.json169 === END ===170 ```171172 Exit codes: `0` pass · `1` a step failed (its `=== NANOCLAW SETUP: … ===`173 block is printed right above the failure; raw output in174 `~/nanoclaw/logs/e2e/<step>.log` on the VM) · `2` no reply / auth error ·175 `3` the host never opened `data/cli.sock` or the socket was unreachable.176 The driver uses `64` for invalid arguments, `65` for checkout/ref errors,177 `66` for an unreadable key, `69` for unconfirmed VM creation/reachability,178 `70` for unconfirmed snapshot creation, and `74` for local result writing/export failure;179 other command failures can return their own nonzero exit code.180181 After installer preflight, `logs/e2e/result.json` records pass/failure,182 exit code, tested commit, tracked changes present at start, phase, service183 type, ping classification and timestamps. An in-progress run has status184 `running`. It replaces a previous run's result and contains no credentials185 or reply text. With `--result-file`, after argument parsing and destination186 validation the driver replaces any old local report with `running` before187 checking the checkout, credentials or VM. It exports a matching completed188 installer result and a sanitized evidence directory before snapshot/removal.189 The evidence binds the run ID, NanoClaw SHA, provider, auth method, provider190 auth-source SHA, dev-tools SHA when available, and exact harness digest. It191 includes checksummed setup/runtime logs and a service/container/socket state192 snapshot. Otherwise the driver records the driver193 failure, exit code, phase and requested ref/commit; `commit` is null because194 no tested revision was confirmed. An interrupted run may remain `running`.195 A failed export keeps the VM. Once exported, the installer result describes196 the test; snapshot/removal errors are reported by the driver's exit code.197198 On the VM, a checkout failure on a base leaves a `running` record with phase199 `checkout` and the requested commit, replacing any old pass. Failures before200 installer preflight on a fresh VM may have no remote result file; the local201 driver report still records the failure.2022033. **Bake a base VM** once the run is green, then clone it for every later run:204205 ```bash206 bash "$E2E_SKILL_DIR/scripts/exe-run.sh" --snapshot my-nanoclaw-e2e-base \207 --provider claude --auth-method api --credential-file /path/to/anthropic-key \208 --result-file /path/to/results/base.json209 bash "$E2E_SKILL_DIR/scripts/exe-run.sh" --base my-nanoclaw-e2e-base --ref my-branch \210 --provider claude --auth-method api --credential-file /path/to/anthropic-key \211 --result-file /path/to/results/cached.json212 ```213214 VM names are global across exe.dev (they become `<name>.exe.xyz`), so215 pick a distinctive snapshot name. Default run names include a random216 suffix. A taken name or invalid create/copy response stops the run; an217 existing VM is never adopted as a replacement. A failed snapshot keeps218 the tested VM, even with `--rm`.219220 Every step is idempotent against the cloned state: `onecli --reuse` when221 `.env` already has `ONECLI_URL`, `auth --check` short-circuits on an222 existing vault secret, `container` rebuilds only what changed,223 `init-cli-agent` reuses the group and wiring. Cached checkouts are pointed224 at the requested repository and exact SHA; tracked edits in a base225 checkout cause a failure instead of being overwritten.2262274. **Poke at it.** `ssh <ssh_dest>` (printed at the end; `ssh exe.dev ls228 --json` lists it), then in `~/nanoclaw`: `pnpm run chat hi`,229 `bin/ncl groups list`, `tail -f logs/nanoclaw.log`.2302315. **Choose retention after evidence is safe.** Recommend deleting a disposable,232 run-owned VM after a pass and retaining an unexpected failure until triage is233 complete. Offer the operator that choice after reporting the result. With234 `--rm`, the driver revalidates its run marker, confirms no harness controller235 is active, calls `ssh exe.dev rm <name> --json`, verifies the name is absent236 from `ssh exe.dev ls --json`, and writes237 `teardown-receipt.json` into the validated artifact directory. Any failed238 export, checksum, redaction, local persistence, ownership, controller, or239 absence check blocks or leaves teardown unconfirmed. For manual cleanup,240 repeat the same checks and record the receipt; inventory lookup alone never241 proves ownership.242243### Running the installer somewhere else244245`e2e-install.sh` has no exe.dev dependency. On a CI runner or any fresh box:246247```bash248git clone https://github.com/nanocoai/nanoclaw-oss-dev-tools.git249E2E_SKILL_DIR="$(pwd)/nanoclaw-oss-dev-tools/skills/e2e-exe-dev"250git clone https://github.com/nanocoai/nanoclaw.git251cd nanoclaw252NANOCLAW_E2E_PROVIDER=claude NANOCLAW_E2E_AUTH_METHOD=api \253NANOCLAW_E2E_AUTH_SOURCE_COMMIT="$(git rev-parse HEAD)" \254NANOCLAW_E2E_KEY_FILE=/path/to/key bash "$E2E_SKILL_DIR/scripts/e2e-install.sh"255```256257The target needs `git` and `python3` before starting the installer. The exe.dev258driver installs these if missing; provision them yourself on other hosts.259260Env it honors: `NANOCLAW_E2E_ROOT`, `NANOCLAW_E2E_KEY_FILE`,261`NANOCLAW_ONECLI_API_HOST` + `NANOCLAW_ONECLI_API_TOKEN` (remote gateway262instead of a local install — the same vars `setup/auto.ts` reads),263`NANOCLAW_DISPLAY_NAME`, `NANOCLAW_E2E_TZ` (default `UTC`),264`NANOCLAW_E2E_KEEP_AGENT` (default `1`), `NANOCLAW_E2E_FORCE_AUTH` (`1`265replaces an existing vault secret with the key file — token rotation on a266`--base` VM whose snapshot still holds the old one).267268The driver forwards the remote gateway settings, display name, timezone,269keep-agent and force-auth settings over stdin into a private environment270file, which is removed after loading. `NANOCLAW_E2E_KEY_FILE` (or `--key-file`)271selects the local key to upload; `NANOCLAW_E2E_ROOT` applies to standalone272installer runs. The driver always uses `~/nanoclaw` on its VM.273274## Compatibility evidence275276On **2026-09-12** a fresh exe.dev VM failed at the `onecli` step against277NanoClaw `fd767d381fe5a1fea51373e4e8cb411da79880ec` with `Port 5432 is already278in use` and nothing listening: the image had started shipping `/exe.dev/bin/sh`279first on PATH (see the gotcha below). With the installer's system-shell280preflight, a new fresh VM passed end to end against281[`d96dde93db7a766531ca288190ca202ad473547e`](https://github.com/nanocoai/nanoclaw/tree/d96dde93db7a766531ca288190ca202ad473547e):282the note line reported `/exe.dev/bin` removed from PATH, every step from283`environment` to `verify` succeeded, `SERVICE_TYPE: nohup`, `PING: ok`, exit 0,284about 3 minutes 20 seconds including OS bootstrap, and the VM was removed with285`--rm`. NanoClaw itself pins `/bin/sh` in those steps from286[nanocoai/nanoclaw#3776](https://github.com/nanocoai/nanoclaw/pull/3776), so287later refs no longer depend on this preflight.288289Fresh and cached VM installations passed on **2026-09-10** against NanoClaw290[`74224f62a6c08418acccc727114ab02f92e403bf`](https://github.com/nanocoai/nanoclaw/tree/74224f62a6c08418acccc727114ab02f92e403bf).291Both produced real model replies, `SERVICE: running`, and successful final292verification using the nohup fallback. The cached run reused OneCLI, the293vault credential, Docker build layers and the existing agent wiring.294295The fresh install passed with skill version `0.2.3`, but its subsequent296snapshot returned driver exit `70`: exe.dev created the copy using a response297shape that the driver did not recognize. Version `0.2.4` accepts that shape298only when its source and destination names match the request. The corrected299driver's live cached-copy run completed with exit `0`; offline regressions300also cover snapshot confirmation and mismatched copy responses.301302Older NanoClaw refs, including `2c754a2234390fcc597273cef6344d99e8ac03d0`,303could fail verification after a successful ping when systemd's user bus was304unavailable. The live-tested ref fixes this by consulting the nohup PID file305when no service is found. Check the selected ref before assuming compatibility.306307## What the installer calls, and why each call is shaped that way308309Everything below is a wizard step or a script that already ships; the310installer adds sequencing and assertions only.311312| # | Call | Source of truth |313|---|---|---|314| 1 | `bash setup.sh` | The launcher's prompt-free bootstrap (`nanoclaw.sh` runs it under a spinner): Node 22 via `setup/install-node.sh`, pnpm via corepack/npm, `pnpm install --frozen-lockfile`, native-module check |315| 2 | `bash setup/install-docker.sh`, then `sg docker` re-exec if the socket is group-gated | `setup/container.ts` does the same for its own step; done once up front because the `onecli` step (a docker-compose install) needs the daemon first |316| 3 | `--step environment` | Informational (`setup/environment.ts` never fails on a missing Docker) — kept for the log |317| 4 | `--step onecli` \| `--reuse` \| `--remote-url <host>` | `setup/onecli.ts`; mode chosen the way `setup/auto.ts` chooses it (`NANOCLAW_ONECLI_API_HOST` → remote; existing install → reuse; else fresh `curl onecli.sh/install \| sh` + CLI from GitHub releases, pinned by `versions.json`) |318| 5 | `--step auth --check`, then `--create --value <key>` only when `STATUS: missing` | `setup/auth.ts`; mirrors `runAuthStep`'s `anthropicSecretExists()` short-circuit |319| 6 | `--step container` | `setup/container.ts`: local `docker build` (or pull when `.env` has `NANOCLAW_HARDENED_IMAGE=true`), then the in-container smoke test |320| 7 | `--step mounts --empty` | The wizard's own args (`setup/auto.ts`); `skipped` on re-runs is fine |321| 8 | `--step timezone --tz <zone>` | `setup/timezone.ts` validates with `isValidTimezone` and writes `TZ` to `.env` |322| 9 | `--step service`, then `./start-nanoclaw.sh` iff `SERVICE_TYPE: nohup` | `setup/service.ts`: builds, **stamps the upgrade marker** (so the host's tripwire passes), installs a system unit as root / user unit otherwise / nohup wrapper without user systemd |323| 10 | Wait for `data/cli.sock`, then `scripts/init-cli-agent.ts --display-name … --agent-name "E2E Agent" --folder e2e-agent` | `src/index.ts` finishes host migrations before opening the CLI socket. Only then run the initializer, which also migrates the DB, to create the `cli:local` scratch user, agent group and wiring without racing fresh host migrations. |324| 11 | `pnpm --silent run chat ping` | `scripts/chat.ts`: exit 0 + reply = ok, 2 = socket unreachable, 3 = no reply within its 120 s stop; the auth-error patterns are the ones `agent-ping.ts` classifies |325| 12 | `--step verify` | `setup/verify.ts`: `success` iff service running ∧ credentials present ∧ (groups > 0 ∨ wiring pending); exits 1 otherwise |326327Step output is parsed from the `=== NANOCLAW SETUP: <STEP> === … STATUS: … === END ===`328block every step prints (`setup/status.ts`).329330## Gotchas (each one cost a wrong assumption)331332- **`sh` on PATH may not be the system shell.** exe.dev images since333 2026-09-09 put `/exe.dev/bin` first on PATH with their own `sh`, whose334 builtin `lsof` always exits 0. NanoClaw's setup pipes downloaded installers335 into `sh` (`setup/onecli.ts`, `setup/install-docker.sh`), so the OneCLI336 installer's port probe reported every port as busy — `Port 5432 is already337 in use (probably a local PostgreSQL)` with nothing listening, and the same338 for `POSTGRES_PORT=5433`. The installer now drops the directory holding a339 foreign `sh` from PATH before the first step (it prints a `note:` line) and340 stops when no system shell exists at `/bin/sh` or `/usr/bin/sh`. The341 product-side fix pins `/bin/sh` in those steps.342- **The wizard is not headless.** `pnpm run setup:auto` prompts for start343 mode, existing-install action and OneCLI reuse with no env bypass, and344 `nanoclaw.sh` reads its root warning from `/dev/tty`. Only the step runner345 (`setup/index.ts --step`) is prompt-free — that's why the installer is346 built on it and never touches `nanoclaw.sh`/`setup:auto`.347- **No user systemd ⇒ nothing starts.** `setupNohupFallback` writes348 `start-nanoclaw.sh` and reports `SERVICE_LOADED: false`; it does not run349 it. The installer starts it. exe.dev VMs are exactly this shape: systemd350 is PID 1 but `systemctl --user` fails ("Failed to connect to bus"), so351 `service` falls back to nohup. `verify.ts` used to check `nanoclaw.pid`352 only when there was no systemd at all and reported `SERVICE: not_found`353 here. The live-tested SHA above fixes this by checking the PID file whenever354 no service is found; older NanoClaw refs may still have the limitation.355- **A non-interactive SSH shell has no `~/.local/bin` on PATH** — `pnpm`,356 `node`, `onecli` all live there. Prefix ad-hoc commands on the VM with357 `export PATH="$HOME/.local/bin:$PATH"`; the installer does this itself.358- **Treat the token as exposed once it has been in any log.** The installer359 redacts `--value` in its own output, but the OAuth token is long-lived:360 rotate it (revoke, `claude setup-token`) after a run whose logs left the361 machine, and re-seed base VMs with `NANOCLAW_E2E_FORCE_AUTH=1`.362- **Keep the agent, or `verify` fails.** The wizard deletes its `ping_test`363 group after the ping (`scripts/delete-cli-agent.ts`) *before* running364 `verify`, which then relies on a channel deferring its wiring. With no365 channel configured that's `registeredGroups: 0` → `failed`. The installer366 keeps `e2e-agent` (set `NANOCLAW_E2E_KEEP_AGENT=0` to delete it after the367 ping; expect `verify` to fail then).368- **The vault is not optional.** Spawn refuses without a gateway369 contribution (`src/gateway-providers/onecli.ts`: "refusing to spawn370 container without credentials"), and `onecli` is the only provider in371 trunk. There is no API-key-in-env shortcut.372- **Docker is the only driver.** `src/drivers/index.ts` ships `docker` alone;373 `NANOCLAW_RUNTIME_DRIVER` set to anything else throws at startup.374- **First container boot is 30–60 s** after the ping is sent; `chat.ts`375 waits up to 120 s and the installer wraps it in `timeout 150`.376- **`GITHUB_TOKEN` in the environment counts as a configured channel** in377 `verify.ts` (`has()` reads `process.env`). The installer unsets it so the378 report describes the install, not the shell.379- **Egress the VM must have:** `deb.nodesource.com`, `get.docker.com`,380 `onecli.sh`, `github.com` (OneCLI CLI release tarball), Docker Hub (base381 image), `api.anthropic.com`. exe.dev VMs have a full network stack; locked-382 down runners do not.383- **The key is on argv for one process.** `auth --create --value` is how384 `setup/auth.ts` accepts it (it then `execFileSync`s `onecli`, no shell), so385 it is visible to `ps` on the VM for that step. Fine for a disposable VM;386 use `NANOCLAW_ONECLI_API_HOST` with a pre-seeded remote vault if not.387- **Don't synthesize the VM hostname.** `new --json` returns `vm_name` and388 `ssh_dest`; the current `cp --json` response uses `name`, `source` and389 `ssh_host`. The driver checks the requested destination name and, for that390 copy format, the source name too. It also accepts the older `vm_name`391 response shape. Modern destinations look like `<name>.exe.xyz`, legacy392 ones like `vm+<name>@vm.exe.xyz`; the driver passes the returned destination393 to SSH. It never falls back to `ls --json` to infer creation success.394- **First contact blocks on the host-key prompt** in a non-interactive395 shell with nothing visible. Every VM connection in the driver carries396 `-o StrictHostKeyChecking=accept-new`.397- **The lobby is not a shell.** `scp`/`sftp`/commands against `exe.dev`398 fail; files go to the VM destination, and the driver uses399 `ssh <vm> 'cat > file' < local` (exe.dev's documented scp-less path).400- **exe.dev `--setup-script` is capped at 10 KiB** and runs at first boot,401 before you can hand it a secret safely. The driver therefore SSHes in after402 boot instead — no size limit, secrets over stdin.403404## Troubleshooting405406| Symptom | Look at | Likely cause |407|---|---|---|408| `setup.sh failed` | `logs/e2e/bootstrap.log` | apt/NodeSource unreachable, or `corepack enable` needed sudo and none was passwordless |409| `docker socket still not accessible under sg docker` | `id -nG`, `ls -l /var/run/docker.sock` | daemon not started; run `sudo systemctl start docker` and re-run |410| `onecli install failed` / `could_not_resolve_api_host` | `logs/e2e/onecli.log`, `logs/setup.log` | `onecli.sh` or GitHub releases blocked; `docker compose` missing from the Docker install |411| `auth --check reported failed` | `onecli secrets list` on the VM | gateway not healthy on port 10254 |412| `container step failed` | `logs/e2e/container.log` | Docker Hub pull blocked, or the smoke test failed inside the image |413| exit `3` (no `data/cli.sock`) | `logs/nanoclaw.error.log` | host crashed at start — read the first ERROR; the upgrade tripwire is not it (`service` stamped the marker) |414| exit `2` with an auth-error reply | the reply text in `logs/e2e/ping.out` | wrong or expired key in the vault; `onecli secrets` to fix, then re-run (auth step short-circuits, so delete the bad secret first) |415| exit `2`, no reply at all | `logs/nanoclaw.log` around the ping timestamp, `bin/ncl sessions list` | container failed to spawn (OneCLI "not applied"), or the agent errored — container logs are gone after exit, so check the outbound DB: `pnpm exec tsx scripts/q.ts data/v2-sessions/<group>/<session>/outbound.db "select * from messages_out"` |416| `verify reported failed` after a green ping | the `SERVICE:` / `CREDENTIALS:` / `REGISTERED_GROUPS:` fields | agent deleted (`KEEP_AGENT=0`); or `SERVICE: not_found` on a nohup-started host — see the source-review limitation above |417| `snapshot … creation was not confirmed` / `NAME creation was not confirmed` | the lobby response right above it | name taken, malformed response, or copy source/destination mismatch — check before retrying |418419## Teardown420421`ssh exe.dev rm <name>` when done — the VM's persistent disk holds the vault422secret. A standalone installer writes setup state and logs into its target423checkout; the driver runs setup on the VM. Uninstall the plugin424with `/plugin uninstall nanoclaw-e2e@nanoclaw-oss-dev-tools`.