Same straggler phrase the README carried — the instance description is
operator-visible in 'incus list', so it follows the rename as well.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fetched https://x.ai/cli/install.sh and read it, rather than inferring
the layout from docs. Three facts, every one of which the template had
wrong:
· the CLI installs as 'grok' (with an 'agent' alias) — NOT 'grok-build'.
So the drill was checking a command that never existed.
· BIN_DIR defaults to $HOME/.grok/bin, and what lands there is a
SYMLINK into the versioned download dir — which is exactly why the
template's 'find -type f' found nothing.
· GROK_BIN_DIR can override the directory.
The install was almost certainly succeeding the whole time; the template
was hunting for the wrong name, as the wrong file type, in the wrong
place. Now it links the known path onto the system PATH, asserts
'grok --version' answers, and dumps what the installer actually left if
the upstream layout ever moves.
Also corrected: the drill's version check (grok, not grok-build), the
agent briefing ('grok login'), the template description, and the README
row. The lesson is the repo's oldest one — read the thing, don't reason
about it.
The first drill run where every failure was the RELEASE CODE, not the
environment. 71 passed, 5 failed; all five traced to four bugs:
1. migrate-host --retire-legacy could NEVER succeed. Re-homing ADDS
user.box=1 but never removed user.claudebox=1, and legacy_boxes()
counted the old tag — so retire saw its own freshly-migrated box as
un-migrated and refused forever ('legacy boxes still exist:
legacybox'), leaving claudenet + claude-dev behind. Now: a verified
re-home drops the legacy tag LAST (after the move is proven, so a
failure anywhere above still leaves the box valid under one tag or
the other), and legacy_boxes() ignores boxes already carrying
user.box=1.
2. box expose died with a bare 'could not add the proxy device' — it
swallowed incus's reason, exactly the sin this repo keeps punishing.
Now it prints incus's error. And the mechanism is corrected: a VM's
proxy needs NAT mode, which requires a static NIC address, so expose
pins the box's current lease first (which also fixes the restart
caveat — the exposure no longer points at a lease the box may lose).
3. wait_box's 2-minute window was too short: the legacy box was declared
dead and then every migration check against it passed. 4 minutes.
4. The grok template hunted for a regular file named exactly
'grok-build' under /home/grok and found nothing — an installer's drop
may be a SYMLINK, and its binary name is upstream's to choose. Now it
tries the plausible names and paths, falls back to any executable
grok*, links both names, and SAYS what it found — or dumps what the
installer actually left when it finds nothing. The drill likewise
dumps the on-disk evidence and the cloud-init log on a --version
failure instead of discarding the box.
Two things:
1. The host scripts are first-class verbs now — 'box setup-host',
'box teardown-host [--purge-incus]', 'box migrate-host --box <n>'.
Nobody should have to run ~/.local/share/box/host/<script>.sh; that
read like an external script and exposed an install path. Each verb
execs the installed script with its flags passed through (same
pattern as 'box doctor'). README, doctor hints, and the uninstall
section point at the verbs now.
2. The repo-runbook convention is '.box/', not '.claudebox/'. Renamed
across docs and the templates' agent briefing; the briefing tells
the agent to read either, and box-recipe.md notes the rename, so
repos still shipping '.claudebox/' keep working through the
transition. (Consuming repos rename their own folder — tracked
separately.)
Also widened the help command column for the longer verb names, and
fixed one sed-casualty where a broad '.claudebox'→'.box' pass had
turned the README's legacy user.claudebox tag into user.box.
The 0.4.0 rename left surface leftovers the user hit through the
CLAUDEBOX_* env vars. Sweep them, drawing a clean line:
box = everything the user touches — env vars (BOX_REPO/REF/HOME/
BIN), installer messages (box-install:), the install tree
(~/.local/share/box, with the installer sweeping the old
~/.local/share/claudebox on upgrade), tool prose, and the
docs (docs/box-{design,recipe}.md).
claudebox = the GitHub repo name (URLs, the claudebox-<ref> tarball
dir, issue refs), the legacy user.claudebox=1 tag, the
old-stack cleanup code (claudenet/claude-dev/claude-isolate/
claudebox-firewall), and the .claudebox/ runbook convention
— a deliberate v1 hold, since renaming it breaks consuming
repos.
Renamed the two doc files and their links; updated drill.sh/doctor.sh
paths and BOX_REPO/BOX_REF; fixed the claude template's in-box briefing
to say 'box'. RUNS.md left as-is (append-only history). No behavior
change beyond the install-dir move, which the installer migrates.
Two coding-CLI templates mirroring claude's shape: a box.env + verbatim
cloud-init, inheriting the box-net placement contract structurally, no
new design.
- codex: OpenAI Codex CLI via 'npm i -g @openai/codex' (the SCOPED
package; needs Node 22), symlinked onto the non-interactive exec PATH
via 'npm prefix -g' — the same PATH fix the claude template needed.
- grok: xAI Grok Build via the official 'curl x.ai/cli/install.sh',
run AS the grok user (the installer drops into $HOME); the binary is
found and symlinked to /usr/local/bin.
Install commands verified upstream at implementation time, per the
issue's rule (npmjs.com/package/@openai/codex, x.ai/cli). Each gets an
AGENTS.md-style context file telling the agent it lives in a
disposable, isolated, creds-free box.
Drill: templates listing now expects four; a compact per-template smoke
(mint, '<cli> --version' via box exec, remove) validates each payload
installs and lands on the exec PATH — the generic mechanic is already
proven by blank+claude and not repeated.
Closes#54
The tool underneath was already generic: a thin, honest wrapper over
Incus. What was Claude-specific was welded on — one image, one profile,
one cloud-init file, one hardcoded 'sudo -u claude'. The weld is now a
template.
The mechanic: 'box new' stamps the template's identity onto the
instance (user.box=1, user.box.template, user.box.user); shell/exec/
tmux read the user back off the instance, and 'incus copy' carries
user.* keys (audit B2), so a clone knows what it is without consulting
the template. Templates are box.env (parsed against a strict allowlist,
never sourced — no key for a network exists, on purpose) plus a
verbatim cloud-init. Every template launches with the shared box-net
profile: the isolated NIC and root disk, nothing template-controlled —
resources land per-instance from box.env, overridable via BOX_CPU/
BOX_MEMORY/BOX_DISK (which is also how the drill shrinks boxes on a
small host now that profile edits can't).
The three open calls, taken as recommended: clean cut at 0.4.0 (no
claudebox shim; the installer retires the old symlink); default
template = claude (muscle memory survives); repo stays heavy-duty/
claudebox, binary is box.
Compat is the tag, not the name: resolve_box and list honor the legacy
user.claudebox=1 forever, and the legacy tag maps to the claude user —
a pre-rename box lists, shells, clones, unchanged.
Deliberate divergence from #17's table: the host-stack resource names
(claudenet, claude-isolate, nft tables, claudebox-firewall.*) are NOT
renamed — they are host-internal, invisible to users, and renaming
them breaks every provisioned host for zero user-visible gain.
claude-dev is no longer created; setup-host creates box-net, teardown
removes both.
Closes#17