fix: run-2 cascade (per-name cleanup), and claude on exec's PATH #20

Merged
dan-claude-bot merged 2 commits from drill/run2-fixes into main 2026-07-13 22:30:17 +00:00
dan-claude-bot commented 2026-07-13 22:22:52 +00:00 (Migrated from github.com)

Run 2 of the drill (#15) scored 42/49 and answered most of the audit. This PR fixes what it exposed — one drill defect and one real claudebox bug — so run 3 can answer the last open probe (A3, sibling isolation).

The cascade (drill defect, 5 of the 7 FAILs)

incus delete -f drill clone archive peer … aborts at the first missing name. Run 1 was interrupted after drill was renamed to archive, so run 2's cleanup died on the missing drill and inherited run 1's boxes: list wasn't empty, rename drill → archive collided with the stale archive, peer hit a UNIQUE constraint (which is also why A3 went unprobed again), and the never-renamed drill survived teardown. Cleanup is now one incus delete per name, and teardown sweeps every name the drill can have left.

The real bug: claudebox exec box -- claude … cannot work on a fresh box

Run 2 proved the binary exists (~/.local/bin holds claude) yet the help's own example invocation fails: cloud-init exports PATH only in .bashrc/.zshrc, which the non-interactive shell behind exec never reads — and since the box's login shell is zsh, even sudo -u claude -i reads neither. Fixed in cloud-init with a symlink into /usr/local/bin, which every shell sees. (Existing boxes/snapshots keep the old behavior until re-minted; claudebox shell was never affected — interactive zsh reads .zshrc.)

Also: the failure diagnostics were reading the escape hatch's claudebox: incus exec … announce line instead of the command output (head -1); they now filter it and additionally probe the binary by full path, and the D-phase docker note reports systemctl is-active docker.

Banked by run 2 (no re-verification needed)

The whole boundary section of #15 minus A3: egress ✓, box→host blocked ✓, RFC1918 dropped ✓, host→box dropped ✓ (A7, new this run), IPv6 off ✓, DNS enumeration leaks as predicted ✓ — and phase D's two decisive answers: dns.mode=none is design-vetoed (it killed all DNS resolution in the box, not just sibling records) and @internal is unsupported on bridge ACLs (Unsupported nftables subject) ⇒ #16 derives the subnet in setup-host.sh. L2 filtering passed (box networking intact).

Re-run after merge, same server (the drill resets run 2's dns.mode/filtering leftovers itself):

cd claudebox && git pull && bash drill/drill.sh --yes

🤖 Generated with Claude Code

Run 2 of the drill (#15) scored 42/49 and answered most of the audit. This PR fixes what it exposed — one drill defect and one real claudebox bug — so run 3 can answer the last open probe (A3, sibling isolation). ## The cascade (drill defect, 5 of the 7 FAILs) `incus delete -f drill clone archive peer …` **aborts at the first missing name**. Run 1 was interrupted after `drill` was renamed to `archive`, so run 2's cleanup died on the missing `drill` and inherited run 1's boxes: `list` wasn't empty, `rename drill → archive` collided with the stale `archive`, `peer` hit a UNIQUE constraint (which is also why A3 went unprobed again), and the never-renamed `drill` survived teardown. Cleanup is now one `incus delete` per name, and teardown sweeps every name the drill can have left. ## The real bug: `claudebox exec box -- claude …` cannot work on a fresh box Run 2 proved the binary exists (`~/.local/bin` holds `claude`) yet the help's own example invocation fails: cloud-init exports PATH only in `.bashrc`/`.zshrc`, which the non-interactive shell behind `exec` never reads — and since the box's login shell is zsh, even `sudo -u claude -i` reads neither. Fixed in cloud-init with a symlink into `/usr/local/bin`, which every shell sees. (Existing boxes/snapshots keep the old behavior until re-minted; `claudebox shell` was never affected — interactive zsh reads `.zshrc`.) Also: the failure diagnostics were reading the escape hatch's `claudebox: incus exec …` announce line instead of the command output (`head -1`); they now filter it and additionally probe the binary by full path, and the D-phase docker note reports `systemctl is-active docker`. ## Banked by run 2 (no re-verification needed) The whole boundary section of #15 minus A3: egress ✓, box→host blocked ✓, RFC1918 dropped ✓, **host→box dropped ✓ (A7, new this run)**, IPv6 off ✓, DNS enumeration **leaks as predicted** ✓ — and phase D's two decisive answers: **`dns.mode=none` is design-vetoed** (it killed all DNS resolution in the box, not just sibling records) and **`@internal` is unsupported on bridge ACLs** (`Unsupported nftables subject`) ⇒ #16 derives the subnet in `setup-host.sh`. L2 filtering passed (box networking intact). Re-run after merge, same server (the drill resets run 2's dns.mode/filtering leftovers itself): ```sh cd claudebox && git pull && bash drill/drill.sh --yes ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/box#20
No description provided.