diff --git a/.planning/STATE.md b/.planning/STATE.md
index 187f20e..779ea63 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-04-09)
Phase: 04 of 3 (sandbox aware prompting)
Plan: Not started
Status: Ready to execute
-Last activity: 2026-04-10
+Last activity: 2026-05-05 - Completed quick task 260505-le7: Add harness config file support to claudebox
Progress: [███░░░░░░░] 33%
@@ -63,6 +63,8 @@ None.
| # | Description | Date | Commit | Directory |
|---|-------------|------|--------|-----------|
| 260410-d4u | on non-nixos hosts, bwrap fails because /etc/static does not exist | 2026-04-10 | 97c10f8 | [260410-d4u-on-non-nixos-hosts-bwrap-fails-because-e](./quick/260410-d4u-on-non-nixos-hosts-bwrap-fails-because-e/) |
+| 260504-bw4 | Add SSH support to claudebox: --with-ssh flag forwards SSH_AUTH_SOCK agent socket, --ssh-key flag mounts specific key files read-only into sandbox ~/.ssh/ | 2026-05-04 | b2aeb2f | [260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl](./quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/) |
+| 260505-le7 | Add harness config file support to claudebox | 2026-05-05 | fbbb355 | [260505-le7-add-harness-config-file-support-to-claud](./quick/260505-le7-add-harness-config-file-support-to-claud/) |
## Session Continuity
diff --git a/.planning/quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/260504-bw4-PLAN.md b/.planning/quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/260504-bw4-PLAN.md
new file mode 100644
index 0000000..8d2405c
--- /dev/null
+++ b/.planning/quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/260504-bw4-PLAN.md
@@ -0,0 +1,290 @@
+---
+phase: 260504-bw4
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - claudebox.sh
+ - README.md
+autonomous: true
+requirements:
+ - SSH-01
+ - SSH-02
+ - SSH-03
+ - SSH-04
+must_haves:
+ truths:
+ - "Running `claudebox --with-ssh` forwards $SSH_AUTH_SOCK into the sandbox at the same path with SSH_AUTH_SOCK env var set"
+ - "Running `claudebox --ssh-key ~/.ssh/id_ed25519` mounts that file (and its .pub if present) read-only into the sandbox at ~/.ssh/id_ed25519"
+ - "When any SSH mechanism is active, ~/.ssh/known_hosts is mounted read-only (if it exists on the host)"
+ - "--ssh-key is repeatable; multiple keys all land in the synthetic sandbox ~/.ssh/"
+ - "--with-ssh and --ssh-key can be combined in one invocation"
+ - "The audit display shows active SSH mechanism(s) and mounts"
+ - "The --dry-run output includes the SSH bwrap flags"
+ - "SANDBOX.md inside the sandbox reflects that SSH is available when SSH flags are active"
+ - "README.md documents SSH usage including ssh-agent setup for bash and fish"
+ artifacts:
+ - path: "claudebox.sh"
+ provides: "--with-ssh and --ssh-key flags, SSH bwrap mounts, conditional SANDBOX.md, audit/dry-run integration"
+ contains: "--with-ssh"
+ - path: "README.md"
+ provides: "SSH section + Flags table entries"
+ contains: "## SSH"
+ key_links:
+ - from: "claudebox.sh flag parser"
+ to: "BWRAP_ARGS SSH mount block"
+ via: "WITH_SSH and SSH_KEYS array set during arg parsing, consumed when assembling bwrap args"
+ pattern: "WITH_SSH|SSH_KEYS"
+ - from: "claudebox.sh SSH state"
+ to: "SANDBOX.md heredoc generation"
+ via: "conditional Default Restrictions text based on WITH_SSH/SSH_KEYS"
+ pattern: "SANDBOX.md"
+ - from: "claudebox.sh SSH state"
+ to: "print_audit + dry-run output"
+ via: "Mounts section emits SSH lines when active"
+ pattern: "print_audit|DRY_RUN"
+---
+
+
+Add two opt-in SSH mechanisms to claudebox so users can `git push/pull` from inside the sandbox without exposing SSH keys by default.
+
+Purpose: Today the sandbox blocks all SSH. Real workflows need it for git remotes. The right answer is opt-in agent forwarding (`--with-ssh`) plus explicit key file mounting (`--ssh-key`), with audit visibility so the user always sees what crossed the boundary.
+
+Output: Updated `claudebox.sh` implementing both flags, audit + dry-run + SANDBOX.md integration, and an updated `README.md` documenting setup and usage.
+
+
+
+@./CLAUDE.md
+@./claudebox.sh
+@./README.md
+@.planning/STATE.md
+
+
+
+
+Flag parsing pattern (lines 9-20):
+```bash
+while (( $# > 0 )); do
+ case "$1" in
+ --yes|-y) SKIP_AUDIT=true ;;
+ ...
+ *) CLAUDE_ARGS+=("$1") ;;
+ esac
+ shift
+done
+```
+
+Audit data structure (lines 240-245): AUDIT_SANDBOX_KEYS / AUDIT_HOST_KEYS / AUDIT_EXTRA_KEYS arrays + parallel _VALS assoc arrays. SSH mounts are mounts, not env vars — they belong in the print_audit `Mounts:` section (line 361), not in the env arrays. SSH_AUTH_SOCK is an env var — it should go through ENV_ARGS via the AUDIT_HOST_KEYS path.
+
+SANDBOX.md generation (lines 185-222): single heredoc with `'SANDBOXEOF'` (literal). To make it conditional we either (a) split it into pieces, or (b) generate it without a quoted heredoc and use bash conditionals. Approach: keep the static parts as a heredoc, then append a conditional "SSH" subsection before writing the "Git" section, OR rewrite as `cat <
+
+
+
+
+
+ Task 1: Implement --with-ssh and --ssh-key flag parsing + bwrap mounts
+ claudebox.sh
+
+ - `--with-ssh` sets WITH_SSH=true. If $SSH_AUTH_SOCK is set and is a socket on the host, add `--bind $SSH_AUTH_SOCK $SSH_AUTH_SOCK` to BWRAP_ARGS and `--setenv SSH_AUTH_SOCK $SSH_AUTH_SOCK` to ENV_ARGS. If the var is unset or the path is not a socket, print a warning to stderr and continue without forwarding.
+ - `--ssh-key ` is repeatable. Each value is appended to a SSH_KEYS array. Path is expanded (`~` -> $HOME) and validated: file must exist and be readable; otherwise exit 1 with an error.
+ - When WITH_SSH=true OR SSH_KEYS is non-empty: add `--dir $HOME/.ssh` to BWRAP_ARGS so the sandbox has a real ~/.ssh directory inside the home tmpfs.
+ - For each key in SSH_KEYS: add `--ro-bind $HOME/.ssh/`. If `.pub` exists on the host, also `--ro-bind .pub $HOME/.ssh/.pub`.
+ - When SSH is active AND `~/.ssh/known_hosts` exists on the host: add `--ro-bind $HOME/.ssh/known_hosts $HOME/.ssh/known_hosts` exactly once (shared between both mechanisms).
+ - The dry-run block (lines 405-451) emits the same SSH lines so `claudebox --dry-run --with-ssh --ssh-key ~/.ssh/id_ed25519` prints them.
+
+
+ 1. In the flag-parsing `case` block (around line 10), add:
+ ```bash
+ --with-ssh) WITH_SSH=true ;;
+ --ssh-key)
+ shift
+ [[ $# -gt 0 ]] || { echo "Error: --ssh-key requires a path" >&2; exit 1; }
+ SSH_KEYS+=("${1/#\~/$HOME}")
+ ;;
+ ```
+ Initialize `WITH_SSH=false` and `SSH_KEYS=()` near the top with the other flag defaults.
+
+ 2. After argument parsing, add a validation+resolution block that:
+ - For each path in SSH_KEYS: resolve to absolute, verify exists+readable, replace array entry with absolute path, error+exit if missing.
+ - If WITH_SSH=true: check `[[ -v SSH_AUTH_SOCK && -S "$SSH_AUTH_SOCK" ]]`. If not, print `${YELLOW}Warning: --with-ssh given but SSH_AUTH_SOCK is unset or not a socket; agent will not be forwarded.${RESET}` and set WITH_SSH=false. (Color vars are defined later — move this block to AFTER the ANSI block at line 107, or use plain text.)
+ - Compute `SSH_ACTIVE=true` if WITH_SSH=true OR ${#SSH_KEYS[@]} > 0; else false.
+ - Compute `KNOWN_HOSTS_MOUNT=true` if SSH_ACTIVE && `[[ -f $HOME/.ssh/known_hosts ]]`.
+
+ 3. Where ENV_ARGS is built (after line 256): if WITH_SSH=true, append `--setenv SSH_AUTH_SOCK $SSH_AUTH_SOCK` and add to AUDIT_HOST_KEYS/VALS so it shows in the audit's `[>]` section.
+
+ 4. Where BWRAP_ARGS is assembled (lines 453-487), after the existing conditional mounts (CLAUDE_JSON, CREDS) and before the trailing `--ro-bind GITCONFIG_TMP ...` line, insert:
+ ```bash
+ if [[ "$SSH_ACTIVE" == true ]]; then
+ BWRAP_ARGS+=(--dir "$HOME/.ssh")
+ if [[ "$WITH_SSH" == true ]]; then
+ BWRAP_ARGS+=(--bind "$SSH_AUTH_SOCK" "$SSH_AUTH_SOCK")
+ fi
+ for key in "${SSH_KEYS[@]}"; do
+ base=$(basename "$key")
+ BWRAP_ARGS+=(--ro-bind "$key" "$HOME/.ssh/$base")
+ if [[ -f "${key}.pub" ]]; then
+ BWRAP_ARGS+=(--ro-bind "${key}.pub" "$HOME/.ssh/$base.pub")
+ fi
+ done
+ if [[ "$KNOWN_HOSTS_MOUNT" == true ]]; then
+ BWRAP_ARGS+=(--ro-bind "$HOME/.ssh/known_hosts" "$HOME/.ssh/known_hosts")
+ fi
+ fi
+ ```
+
+ 5. Mirror all of (4) in the dry-run echo block (after the CREDS_MOUNT block around line 444, before the GITCONFIG line at 445), printing the same flags as quoted strings.
+
+ 6. Update the audit display (`print_audit`, around line 361) to emit additional Mounts lines when SSH_ACTIVE:
+ - `agent (read-write, --with-ssh)` if WITH_SSH
+ - For each key: `ssh-key (read-only)`; add ` + .pub` line if pub exists
+ - `known_hosts (read-only)` if KNOWN_HOSTS_MOUNT
+
+
+ bash -n claudebox.sh && claudebox --dry-run --with-ssh 2>&1 | grep -q "SSH_AUTH_SOCK\|Warning: --with-ssh" && echo "Note: full agent forwarding only verifiable when ssh-agent is running on host"
+
+
+ - `claudebox --dry-run --with-ssh` (with agent running) prints `--bind $SSH_AUTH_SOCK ...` and `--setenv SSH_AUTH_SOCK ...`.
+ - `claudebox --dry-run --ssh-key ~/.ssh/id_ed25519` prints `--dir $HOME/.ssh`, `--ro-bind $HOME/.ssh/id_ed25519`, and (if present) the matching .pub bind.
+ - Both flags together print all of the above plus a single `--ro-bind .../known_hosts ...` line (if known_hosts exists).
+ - Missing key file → `claudebox --ssh-key /nonexistent` exits 1 with a clear error.
+ - Audit display shows the SSH mounts in the `Mounts:` section.
+ - `bash -n claudebox.sh` passes; shellcheck (run by writeShellApplication at build time) passes.
+
+
+
+
+ Task 2: Make SANDBOX.md conditional on SSH activation
+ claudebox.sh
+
+ - When SSH_ACTIVE=false: SANDBOX.md keeps the current "Default Restrictions" section listing SSH keys as not mounted, and the Git section recommends HTTPS.
+ - When SSH_ACTIVE=true: "Default Restrictions" no longer lists SSH keys; a new "SSH" subsection states which mechanism is active (agent forwarding via $SSH_AUTH_SOCK and/or explicit key files at ~/.ssh/), and the Git section drops the HTTPS-preference sentence (or replaces it with: "SSH remotes work in this session.").
+
+
+ 1. Replace the quoted heredoc at lines 185-222 with an unquoted heredoc using shell-side composed variables. Build them before the heredoc:
+ ```bash
+ if [[ "$SSH_ACTIVE" == true ]]; then
+ _SSH_NOTES=""
+ [[ "$WITH_SSH" == true ]] && _SSH_NOTES+="- ssh-agent socket forwarded via \$SSH_AUTH_SOCK\n"
+ (( ${#SSH_KEYS[@]} > 0 )) && _SSH_NOTES+="- Explicit key file(s) mounted read-only at ~/.ssh/\n"
+ SANDBOX_RESTRICTIONS_BLOCK=$'## Default Restrictions\n\nBy default, the following are not mounted into the sandbox:\n- GPG and age keys (~/.gnupg, age key files)\n- Cloud credentials (~/.aws, ~/.config/gcloud)\n- Tailscale state\n\n## SSH\n\nSSH is available in this session:\n'"$(printf "$_SSH_NOTES")"$'\nUse `git push`/`git pull` over SSH normally.'
+ SANDBOX_GIT_TAIL="SSH remotes work in this session."
+ else
+ SANDBOX_RESTRICTIONS_BLOCK=$'## Default Restrictions\n\nBy default, the following are not mounted into the sandbox:\n- SSH keys (~/.ssh)\n- GPG and age keys (~/.gnupg, age key files)\n- Cloud credentials (~/.aws, ~/.config/gcloud)\n- Tailscale state\n\nIf your setup has been customized, some of these may be available.'
+ SANDBOX_GIT_TAIL="For remote operations, prefer HTTPS URLs over SSH since SSH keys are not available by default."
+ fi
+ ```
+ 2. Rewrite the heredoc as `cat > "$HOME/.claudebox/SANDBOX.md" <
+
+ bash -n claudebox.sh && claudebox --dry-run -y >/dev/null 2>&1 && grep -q "SSH keys (~/.ssh)" "$HOME/.claudebox/SANDBOX.md" && echo "no-ssh path OK" && claudebox --dry-run -y --ssh-key /etc/hostname >/dev/null 2>&1 && grep -q "## SSH" "$HOME/.claudebox/SANDBOX.md" && ! grep -q "SSH keys (~/.ssh)" "$HOME/.claudebox/SANDBOX.md" && echo "ssh-active path OK"
+
+
+ - Without SSH flags: SANDBOX.md contains "SSH keys (~/.ssh)" in restrictions and HTTPS preference in Git section.
+ - With `--with-ssh` or `--ssh-key`: SANDBOX.md drops the SSH-keys restriction line, gains a "## SSH" section listing active mechanisms, and Git section says SSH works.
+ - `bash -n` and shellcheck pass.
+
+
+
+
+ Task 3: Document SSH support in README.md
+ README.md
+
+ - Flags table includes `--with-ssh` and `--ssh-key ` rows with concise descriptions.
+ - New `## SSH` section after `## Env vars` (and before `## How it works`) covers: when you need SSH (git push/pull over SSH remotes), the agent-forwarding flow with bash and fish setup commands, the agent-dies-with-shell caveat, the explicit key-file flow, and guidance on when to prefer each.
+
+
+ 1. Update the Flags table (lines 34-41) by inserting two rows after `--shell`:
+ ```
+ | `--with-ssh` | Forward $SSH_AUTH_SOCK into the sandbox (requires running ssh-agent) |
+ | `--ssh-key ` | Mount a private key file read-only into the sandbox ~/.ssh/ (repeatable) |
+ ```
+ 2. Add a new `## SSH` section between the existing `## Env vars` and `## How it works` sections with this content (verbatim shape, write in the same plain-prose tone as the rest of the README — no marketing fluff):
+
+ ```markdown
+ ## SSH
+
+ SSH is opt-in. By default no keys or agent socket cross the sandbox boundary, which means git push/pull over SSH remotes won't work. Two mechanisms are available — pick whichever matches your workflow.
+
+ ### `--with-ssh` (agent forwarding)
+
+ Forwards `$SSH_AUTH_SOCK` into the sandbox so any keys loaded in your ssh-agent are usable inside. Your private key files are never mounted; only the agent socket is.
+
+ Start an agent before launching claudebox. The agent dies with the shell that started it, so don't expect it to survive across terminals.
+
+ Bash:
+ ```bash
+ eval "$(ssh-agent)"
+ ssh-add ~/.ssh/id_ed25519
+ claudebox --with-ssh
+ ```
+
+ Fish:
+ ```fish
+ eval (ssh-agent -c)
+ ssh-add ~/.ssh/id_ed25519
+ claudebox --with-ssh
+ ```
+
+ If `--with-ssh` is passed but no agent is running, claudebox warns and continues without forwarding.
+
+ ### `--ssh-key ` (explicit key files)
+
+ Mounts a specific private key (and matching `.pub`, if present) read-only into the sandbox at `~/.ssh/`. Repeatable — pass it multiple times for multiple keys.
+
+ ```bash
+ claudebox --ssh-key ~/.ssh/id_ed25519
+ claudebox --ssh-key ~/.ssh/id_work --ssh-key ~/.ssh/id_personal
+ ```
+
+ Prefer this when you don't have an agent running, or when you want to scope exactly which keys the sandbox can use regardless of what's loaded in the agent.
+
+ ### known_hosts
+
+ When either flag is active, `~/.ssh/known_hosts` is mounted read-only (if it exists) so SSH host verification works without prompting.
+
+ Both flags can be combined.
+ ```
+
+
+ grep -q "^## SSH" README.md && grep -q "\-\-with-ssh" README.md && grep -q "\-\-ssh-key" README.md && grep -q "ssh-agent -c" README.md && grep -q "known_hosts" README.md
+
+
+ - README.md Flags table lists both new flags.
+ - README.md has a `## SSH` section with bash + fish agent setup, explicit-key usage, and known_hosts note.
+ - No broken markdown structure (sections in order, code fences balanced).
+
+
+
+
+
+
+End-to-end smoke checks:
+
+1. `bash -n claudebox.sh` — syntax valid.
+2. `nix build` (or `nix flake check`) succeeds — shellcheck via writeShellApplication passes.
+3. `claudebox --dry-run` (no SSH flags) — output contains no `--bind $SSH_AUTH_SOCK`, no `~/.ssh` mounts.
+4. With agent running: `eval "$(ssh-agent)" && claudebox --dry-run --with-ssh` — output contains `--bind ` and `--setenv SSH_AUTH_SOCK ...`.
+5. `claudebox --dry-run --ssh-key ~/.ssh/id_ed25519` (assuming key exists) — output contains `--dir $HOME/.ssh`, `--ro-bind $HOME/.ssh/id_ed25519`, and `.pub` bind if present.
+6. `claudebox --dry-run --ssh-key /nonexistent` — exits non-zero with clear error.
+7. SANDBOX.md content matches SSH state (verify by inspecting `~/.claudebox/SANDBOX.md` after a dry run with and without flags).
+8. README renders correctly (visual or `, glow README.md`).
+
+
+
+- All three tasks complete and `` criteria met.
+- Both flags work in isolation, together, and respect missing-input failure modes.
+- Audit display + dry-run + SANDBOX.md all reflect SSH state consistently.
+- README documents the feature for both bash and fish users.
+- No regressions: running `claudebox` without any SSH flag behaves exactly as before.
+
+
+
diff --git a/.planning/quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/260504-bw4-SUMMARY.md b/.planning/quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/260504-bw4-SUMMARY.md
new file mode 100644
index 0000000..c68f26a
--- /dev/null
+++ b/.planning/quick/260504-bw4-add-ssh-support-to-claudebox-with-ssh-fl/260504-bw4-SUMMARY.md
@@ -0,0 +1,68 @@
+---
+phase: 260504-bw4
+plan: 01
+subsystem: sandbox/ssh
+tags: [ssh, bwrap, security, opt-in]
+dependency_graph:
+ requires: []
+ provides: [ssh-agent-forwarding, ssh-key-mounts, sandbox-ssh-awareness]
+ affects: [claudebox.sh, README.md]
+tech_stack:
+ added: []
+ patterns: [opt-in SSH via bwrap --bind/--ro-bind, conditional SANDBOX.md generation]
+key_files:
+ modified:
+ - claudebox.sh
+ - README.md
+decisions:
+ - SSH is opt-in: no keys or sockets cross the sandbox boundary without explicit flags
+ - --with-ssh validation: silently degrades to no-op with warning if ssh-agent is not running
+ - SANDBOX.md uses unquoted heredoc with pre-composed variables for conditional content
+ - known_hosts mounted once if either SSH mechanism is active (shared between --with-ssh and --ssh-key)
+metrics:
+ duration: 8min
+ completed: 2026-05-04
+ tasks: 3
+ files: 2
+---
+
+# Quick Task 260504-bw4: Add SSH Support to claudebox Summary
+
+One-liner: Opt-in SSH via `--with-ssh` (agent socket forwarding) and `--ssh-key` (explicit key file mounts), with audit/dry-run/SANDBOX.md integration and README documentation.
+
+## Tasks Completed
+
+| Task | Name | Commit | Files |
+|------|------|--------|-------|
+| 1 | Implement --with-ssh and --ssh-key flag parsing + bwrap mounts | 41ebf10 | claudebox.sh |
+| 2 | Make SANDBOX.md conditional on SSH activation | e9154fd | claudebox.sh |
+| 3 | Document SSH support in README.md | b2aeb2f | README.md |
+
+## What Was Built
+
+**claudebox.sh** now accepts two new flags:
+
+- `--with-ssh`: validates `$SSH_AUTH_SOCK` is a real socket, adds `--bind $SSH_AUTH_SOCK $SSH_AUTH_SOCK` and `--setenv SSH_AUTH_SOCK` to bwrap args, degrades gracefully with a warning if no agent is running.
+- `--ssh-key `: repeatable, validates file exists+readable, mounts key (and `.pub` if present) read-only into `~/.ssh/` inside the sandbox.
+- When either mechanism is active: `--dir ~/.ssh` is added, and `~/.ssh/known_hosts` is mounted read-only if it exists on the host.
+- Audit display shows SSH mounts in the Mounts section.
+- `--dry-run` output mirrors all SSH bwrap flags.
+- SANDBOX.md is now generated conditionally: no-SSH mode lists SSH keys in restrictions and recommends HTTPS; SSH-active mode drops that restriction, adds a `## SSH` section describing which mechanisms are active, and says SSH remotes work.
+
+**README.md** gains two flag table rows and a `## SSH` section covering both mechanisms, bash/fish agent setup, the agent-lifetime caveat, explicit key usage, and the known_hosts note.
+
+## Deviations from Plan
+
+None - plan executed exactly as written.
+
+## Threat Flags
+
+No new threat surface introduced. SSH flags are opt-in and explicitly documented. The agent socket bind is scope-limited to `--bind $SSH_AUTH_SOCK $SSH_AUTH_SOCK` (only the socket path the user explicitly opts into). Key files are read-only.
+
+## Self-Check: PASSED
+
+- claudebox.sh: FOUND
+- README.md: FOUND
+- 41ebf10 (Task 1): FOUND
+- e9154fd (Task 2): FOUND
+- b2aeb2f (Task 3): FOUND
diff --git a/.planning/quick/260505-le7-add-harness-config-file-support-to-claud/260505-le7-PLAN.md b/.planning/quick/260505-le7-add-harness-config-file-support-to-claud/260505-le7-PLAN.md
new file mode 100644
index 0000000..8ee4f3e
--- /dev/null
+++ b/.planning/quick/260505-le7-add-harness-config-file-support-to-claud/260505-le7-PLAN.md
@@ -0,0 +1,369 @@
+---
+phase: 260505-le7
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - claudebox.sh
+autonomous: true
+requirements: [HARNESS-01, HARNESS-02, HARNESS-03, HARNESS-04]
+
+must_haves:
+ truths:
+ - "User can put `cmd = gsd` in `~/.claudebox/config` or `/.claudebox` and claudebox launches gsd inside the sandbox instead of claude"
+ - "User can put `mount_home = .gsd` in a config file and `~/.gsd` is rw-bound into the sandbox"
+ - "User can put `path_add = ~/.local/share/npm/bin` in a config file and that dir appears prepended to PATH inside the sandbox"
+ - "Per-project `.claudebox` overrides global `~/.claudebox/config` for `cmd` (last-wins), but `mount_home` and `path_add` accumulate across both files"
+ - "CLI flags `--cmd`, `--mount-home`, `--path-add` override/append on top of config files with same semantics"
+ - "When `cmd` resolves to anything other than the default `claude` binary, `--dangerously-skip-permissions` is NOT prepended"
+ - "Audit shows a `[config]` section listing which config files were loaded; extra mounts appear in Mounts section; path additions appear in PATH list"
+ - "`--dry-run` reflects extra mounts and PATH additions"
+ - "`--check` reports presence/absence of `~/.claudebox/config` and `/.claudebox`"
+ - "If `cmd` resolves to a non-existent binary, claudebox prints an error and exits 1"
+ - "If a `mount_home` subdir does not exist on host, claudebox warns but does not error"
+ artifacts:
+ - path: "claudebox.sh"
+ provides: "Config file parsing, CLI flag handling, harness binary resolution, extra mounts, PATH augmentation"
+ contains: "load_config_file"
+ key_links:
+ - from: "config file parser"
+ to: "MOUNT_HOME / PATH_ADD / HARNESS_CMD globals"
+ via: "load_config_file appends to arrays / sets scalar"
+ pattern: "load_config_file"
+ - from: "HARNESS_CMD"
+ to: "SANDBOX_CMD construction"
+ via: "command -v resolution; conditional --dangerously-skip-permissions"
+ pattern: "SANDBOX_CMD=.*HARNESS_BIN"
+ - from: "PATH_ADD entries"
+ to: "SANDBOX_PATH passed via --setenv PATH"
+ via: "prepend with `:` separator before bwrap exec"
+ pattern: "SANDBOX_PATH="
+ - from: "MOUNT_HOME entries"
+ to: "BWRAP_ARGS / dry-run output"
+ via: "--bind \\$HOME/ \\$HOME/"
+ pattern: "--bind.*HOME"
+---
+
+
+Add config file support to `claudebox` so users can pin alternate harnesses (e.g. `gsd`) and accompanying home mounts / PATH dirs without remembering CLI flags.
+
+Purpose: Make claudebox usable as a generic sandbox launcher for non-claude CLIs while keeping claude as the zero-config default.
+
+Output: Updated `claudebox.sh` with config file loading, CLI flag overrides, harness binary resolution with conditional `--dangerously-skip-permissions`, extra home mounts, PATH augmentation, audit/dry-run/check integration.
+
+
+
+@/home/toph/code/tools/claudebox/.claude/get-shit-done/workflows/execute-plan.md
+
+
+
+@/home/toph/code/tools/claudebox/CLAUDE.md
+@/home/toph/code/tools/claudebox/claudebox.sh
+@/home/toph/code/tools/claudebox/flake.nix
+
+
+
+
+Current flag parser (lines 11-28): while-loop case statement, `--` ends parsing, unknown args fall through to `CLAUDE_ARGS+=("$1")`.
+
+Existing globals set during init:
+- `CWD` (line 178)
+- `CANONICAL_ROOT` (line 199) — also used to locate per-project `.claudebox.env`; reuse for `/.claudebox`
+- `HOME`, `SANDBOX_PATH` (injected by flake.nix), `SANDBOX_BASH`, `CLAUDE_BIN`
+
+Existing parallel pattern to mimic — env files (lines 386-408):
+```bash
+load_env_file() {
+ local file="$1"
+ [[ -f "$file" ]] || return 0
+ while IFS= read -r line || [[ -n "$line" ]]; do
+ line="${line#"${line%%[! ]*}"}"
+ [[ -z "$line" || "$line" == '#'* ]] && continue
+ [[ "$line" != *=* ]] && continue
+ local key="${line%%=*}"
+ local val="${line#*=}"
+ ...
+ done < "$file"
+}
+load_env_file "$HOME/.claudebox/env"
+load_env_file "$CANONICAL_ROOT/.claudebox.env"
+```
+
+SANDBOX_CMD construction (lines 492-496):
+```bash
+if [[ "$SHELL_MODE" == true ]]; then
+ SANDBOX_CMD=("$SANDBOX_BASH" "${CLAUDE_ARGS[@]}")
+else
+ SANDBOX_CMD=("$CLAUDE_BIN" --dangerously-skip-permissions "${CLAUDE_ARGS[@]}")
+fi
+```
+
+BWRAP_ARGS assembly (lines 565-624) and dry-run output (lines 499-563) are parallel — any new mount must be added to BOTH.
+
+ENV_ARGS already wires `--setenv PATH "$SANDBOX_PATH"`. To prepend dirs, mutate `SANDBOX_PATH` BEFORE building ENV_ARGS (line 320) OR after, and update `AUDIT_SANDBOX_VALS[PATH]` to match.
+
+
+
+
+
+
+ Task 1: Parse config files and CLI flags; expose HARNESS_CMD / MOUNT_HOME / PATH_ADD globals
+ claudebox.sh
+
+Add config file parsing and new CLI flags to `claudebox.sh`.
+
+1. **Initialise globals** near the top (alongside `SKIP_AUDIT=false` etc., line 1-9):
+ ```bash
+ HARNESS_CMD="" # set by config or --cmd; empty means "use default claude"
+ MOUNT_HOME=() # array of subdir names (relative to $HOME)
+ PATH_ADD=() # array of dirs to prepend to sandbox PATH
+ CONFIG_FILES_LOADED=() # for audit: list of loaded config paths
+ ```
+
+2. **Add CLI flags** to the while-loop (lines 11-28):
+ - `--cmd ` → sets `HARNESS_CMD="$2"; shift`
+ - `--mount-home ` → `MOUNT_HOME+=("$2"); shift`
+ - `--path-add ` → `PATH_ADD+=("${2/#\~/$HOME}"); shift`
+ - All three must validate the next arg exists and error+exit 1 if missing (mirror the `--ssh-key` pattern at lines 19-22).
+
+3. **Add config loader function** alongside `load_env_file` (after line 408 is fine, but it MUST run BEFORE flag parsing applies overrides — see step 4 for ordering). Function:
+ ```bash
+ load_config_file() {
+ local file="$1"
+ [[ -f "$file" ]] || return 0
+ CONFIG_FILES_LOADED+=("$file")
+ while IFS= read -r line || [[ -n "$line" ]]; do
+ line="${line#"${line%%[! ]*}"}" # ltrim
+ [[ -z "$line" || "$line" == '#'* ]] && continue
+ [[ "$line" != *=* ]] && continue
+ local key="${line%%=*}"
+ local val="${line#*=}"
+ # trim surrounding whitespace from key and val
+ key="${key%"${key##*[! ]}"}"; key="${key#"${key%%[! ]*}"}"
+ val="${val#"${val%%[! ]*}"}"; val="${val%"${val##*[! ]}"}"
+ case "$key" in
+ cmd) HARNESS_CMD="$val" ;;
+ mount_home) MOUNT_HOME+=("$val") ;;
+ path_add) PATH_ADD+=("${val/#\~/$HOME}") ;;
+ *) echo "${YELLOW:-}Warning: unknown key '$key' in $file${RESET:-}" >&2 ;;
+ esac
+ done < "$file"
+ }
+ ```
+
+4. **Ordering constraint (CRITICAL)**: Config files must load BEFORE CLI flags take effect, but CANONICAL_ROOT is computed at line 199 — AFTER current flag parsing. Solution: split into two passes.
+ - Move `compute_canonical_root` + `CANONICAL_ROOT` computation earlier (right after `CWD=$(pwd)` at line 178), so it is available before config loading. Verify nothing earlier in the script depends on `CWD` having NOT been canonicalised — it doesn't, only `CWD` itself is used.
+ - Then load configs in this order (cascading, later overrides earlier scalar / appends to arrays):
+ ```bash
+ load_config_file "$HOME/.claudebox/config"
+ load_config_file "$CANONICAL_ROOT/.claudebox"
+ ```
+ - The CLI flag handling already happens at the top of the script. To preserve "CLI overrides config" semantics, capture the CLI values into separate variables during arg parsing (e.g. `CLI_HARNESS_CMD`, `CLI_MOUNT_HOME=()`, `CLI_PATH_ADD=()`), then after config loading apply:
+ ```bash
+ [[ -n "$CLI_HARNESS_CMD" ]] && HARNESS_CMD="$CLI_HARNESS_CMD"
+ MOUNT_HOME+=("${CLI_MOUNT_HOME[@]}")
+ PATH_ADD+=("${CLI_PATH_ADD[@]}")
+ ```
+ Note the bash gotcha: `MOUNT_HOME+=("${CLI_MOUNT_HOME[@]}")` errors under `set -u` when the array is empty. Use `MOUNT_HOME+=("${CLI_MOUNT_HOME[@]:-}")` guarded by length check, or `(( ${#CLI_MOUNT_HOME[@]} > 0 )) && MOUNT_HOME+=("${CLI_MOUNT_HOME[@]}")`.
+
+5. **Resolve harness binary**. After `CLAUDE_BIN="$(command -v claude)"` (line 175), add:
+ ```bash
+ if [[ -n "$HARNESS_CMD" ]]; then
+ HARNESS_BIN="$(command -v "$HARNESS_CMD" 2>/dev/null)" || {
+ echo "${RED:-}Error: configured cmd '$HARNESS_CMD' not found in PATH${RESET:-}" >&2
+ exit 1
+ }
+ else
+ HARNESS_BIN="$CLAUDE_BIN"
+ HARNESS_CMD="claude"
+ fi
+ IS_DEFAULT_CLAUDE=false
+ [[ "$HARNESS_BIN" == "$CLAUDE_BIN" ]] && IS_DEFAULT_CLAUDE=true
+ ```
+ Note ANSI colour vars are defined at line 121 — make sure binary resolution happens AFTER that block so error styling works. If config loading must happen before colour-var setup for ordering, fall back to plain text in the error.
+
+6. **`--check` additions**: in the CHECK_MODE block (lines 71-112), after the `~/.claudebox` check, add:
+ ```bash
+ if [[ -f "$HOME/.claudebox/config" ]]; then
+ echo "${green}OK${reset} ~/.claudebox/config exists" >&2
+ else
+ echo "${yellow}WARN${reset} ~/.claudebox/config -- not found (optional)" >&2
+ fi
+ _proj_cfg=$(compute_canonical_root "$PWD")/.claudebox
+ if [[ -f "$_proj_cfg" ]]; then
+ echo "${green}OK${reset} $_proj_cfg exists" >&2
+ else
+ echo "${yellow}WARN${reset} $_proj_cfg -- not found (optional)" >&2
+ fi
+ ```
+ `compute_canonical_root` is defined at line 181; the function definition must be moved above the CHECK_MODE block too (or duplicated check moved below — simpler: hoist the function definition near the top with the other helpers, alongside `gc_instances`).
+
+Do not introduce new external dependencies. Keep everything in pure bash. Preserve `set -euo pipefail` semantics imposed by `writeShellApplication`.
+
+
+ cd /home/toph/code/tools/claudebox && nix build .#claudebox 2>&1 | tail -20 && ./result/bin/claudebox --check 2>&1 | grep -E '(claudebox/config|/.claudebox)'
+
+
+- New flags `--cmd`, `--mount-home`, `--path-add` parse without error
+- `nix build .#claudebox` succeeds (shellcheck clean)
+- `--check` reports presence/absence of both config file paths
+- HARNESS_BIN resolves correctly; missing harness binary produces error+exit 1
+- `IS_DEFAULT_CLAUDE` flag correctly distinguishes default-claude from override
+
+
+
+
+ Task 2: Wire HARNESS_BIN, MOUNT_HOME, PATH_ADD into SANDBOX_CMD, BWRAP_ARGS, ENV_ARGS, audit, and dry-run
+ claudebox.sh
+
+Consume the globals produced by Task 1 throughout the rest of the pipeline.
+
+1. **PATH augmentation**. BEFORE building `ENV_ARGS` (currently at line 320), prepend PATH_ADD entries to SANDBOX_PATH:
+ ```bash
+ if (( ${#PATH_ADD[@]} > 0 )); then
+ _path_prefix=""
+ for _p in "${PATH_ADD[@]}"; do
+ _path_prefix+="${_p}:"
+ done
+ SANDBOX_PATH="${_path_prefix}${SANDBOX_PATH}"
+ unset _path_prefix _p
+ fi
+ ```
+ This mutates SANDBOX_PATH before it is consumed by `--setenv PATH "$SANDBOX_PATH"` and by `AUDIT_SANDBOX_VALS[PATH]` — both will reflect the prepended dirs automatically. Verify by inspecting lines 324 and 333 use `$SANDBOX_PATH` directly.
+
+2. **MOUNT_HOME validation and bwrap wiring**. After the existing SSH-related mount block in BWRAP_ARGS (after line 616, before the closing `BWRAP_ARGS+=(...)` block at 618):
+ ```bash
+ for _sub in "${MOUNT_HOME[@]}"; do
+ _src="$HOME/$_sub"
+ if [[ ! -e "$_src" ]]; then
+ echo "${YELLOW}Warning: mount_home '$_sub' does not exist at $_src; skipping${RESET}" >&2
+ continue
+ fi
+ BWRAP_ARGS+=(--bind "$_src" "$_src")
+ done
+ unset _sub _src
+ ```
+ Mirror this in the dry-run block (after line 555, before line 557 `--ro-bind .gitconfig`):
+ ```bash
+ for _dry_sub in "${MOUNT_HOME[@]}"; do
+ _dry_src="$HOME/$_dry_sub"
+ [[ -e "$_dry_src" ]] || continue
+ echo " --bind $_dry_src $_dry_src \\"
+ done
+ unset _dry_sub _dry_src
+ ```
+
+3. **SANDBOX_CMD construction**. Replace lines 492-496:
+ ```bash
+ if [[ "$SHELL_MODE" == true ]]; then
+ SANDBOX_CMD=("$SANDBOX_BASH" "${CLAUDE_ARGS[@]}")
+ elif [[ "$IS_DEFAULT_CLAUDE" == true ]]; then
+ SANDBOX_CMD=("$HARNESS_BIN" --dangerously-skip-permissions "${CLAUDE_ARGS[@]}")
+ else
+ SANDBOX_CMD=("$HARNESS_BIN" "${CLAUDE_ARGS[@]}")
+ fi
+ ```
+
+4. **Audit display additions** in `print_audit()` (lines 411-470):
+ - At the very top of the function (before the `=== Sandbox Environment ===` header), add a config block when configs were loaded:
+ ```bash
+ if (( ${#CONFIG_FILES_LOADED[@]} > 0 )); then
+ echo "${BOLD}${CYAN}=== Config ===${RESET}" >&2
+ for _cf in "${CONFIG_FILES_LOADED[@]}"; do
+ echo " loaded: $_cf" >&2
+ done
+ echo " cmd=$HARNESS_CMD ($HARNESS_BIN)" >&2
+ (( ${#MOUNT_HOME[@]} > 0 )) && echo " mount_home: ${MOUNT_HOME[*]}" >&2
+ (( ${#PATH_ADD[@]} > 0 )) && echo " path_add: ${PATH_ADD[*]}" >&2
+ echo "" >&2
+ unset _cf
+ fi
+ ```
+ Even when no config file is loaded but `HARNESS_CMD != claude` (set via `--cmd`), still show the cmd line so users know they're not running stock claude. Adjust the condition: show the section if `${#CONFIG_FILES_LOADED[@]} > 0 || $IS_DEFAULT_CLAUDE != true`.
+ - In the Mounts section (lines 439-463), after the credentials/SSH blocks, add:
+ ```bash
+ for _sub in "${MOUNT_HOME[@]}"; do
+ _src="$HOME/$_sub"
+ [[ -e "$_src" ]] || continue
+ printf ' %-12s %s (read-write, mount_home)\n' "home" "$_src" >&2
+ done
+ unset _sub _src
+ ```
+ - PATH addition is already visible in the audit because we mutated `SANDBOX_PATH` in step 1; no further change needed (the per-entry display at lines 419-422 will show prepended dirs first).
+
+5. Sanity-check shellcheck cleanliness — `writeShellApplication` runs shellcheck at build. Common gotchas:
+ - Empty array expansion under `set -u`: guard with `(( ${#arr[@]} > 0 ))` or use `"${arr[@]:-}"`
+ - Unused vars when arrays are empty: prefix with `_` or use `unset` after the loop
+ - `local` only valid inside functions
+
+Do not modify mount logic for existing `~/.claude`, history, SANDBOX.md, credentials, or SSH paths. Do not add any package to `runtimeDeps` in `flake.nix` — pure bash only.
+
+
+ cd /home/toph/code/tools/claudebox && nix build .#claudebox 2>&1 | tail -5 && mkdir -p /tmp/le7-test && printf 'cmd = bash\nmount_home = .config\npath_add = ~/.local/bin\n' > /tmp/le7-test/.claudebox && cd /tmp/le7-test && git init -q && /home/toph/code/tools/claudebox/result/bin/claudebox --dry-run --yes 2>&1 | grep -E '(bash|--bind.*\.config|/\.local/bin)' && cd - && rm -rf /tmp/le7-test
+
+
+- `nix build .#claudebox` succeeds (shellcheck clean, no runtime errors)
+- Per-project `.claudebox` with `cmd = bash` causes dry-run to show `bash` (not `claude --dangerously-skip-permissions`) as the final exec target
+- `mount_home = .config` produces a `--bind $HOME/.config $HOME/.config` line in dry-run
+- `path_add = ~/.local/bin` causes `~/.local/bin` to appear at the front of the `--setenv PATH` value in dry-run
+- Audit output shows `=== Config ===` block listing loaded files when configs are in play
+- Default invocation (no config, no flags) is byte-identical in behaviour to current claudebox: claude is launched with `--dangerously-skip-permissions`
+- Non-existent `mount_home` subdir produces a warning but does not abort
+- Non-existent `cmd` binary aborts with exit 1 and an error message
+
+
+
+
+
+
+After both tasks complete, run the full integration check:
+
+```bash
+nix build .#claudebox
+
+# 1. Default behaviour preserved (no config, no flags)
+./result/bin/claudebox --dry-run --yes 2>&1 | grep -- '--dangerously-skip-permissions' || echo "FAIL: default claude invocation lost"
+
+# 2. Per-project .claudebox with custom cmd
+mkdir -p /tmp/le7-int && cd /tmp/le7-int && git init -q
+printf 'cmd = bash\n' > .claudebox
+/home/toph/code/tools/claudebox/result/bin/claudebox --dry-run --yes 2>&1 | tail -3 | grep -v -- '--dangerously-skip-permissions' | grep -q bash && echo "OK: harness override works"
+
+# 3. mount_home + path_add
+printf 'cmd = bash\nmount_home = .config\npath_add = ~/.local/bin\n' > .claudebox
+/home/toph/code/tools/claudebox/result/bin/claudebox --dry-run --yes 2>&1 | grep -E '(\.local/bin|--bind.*\.config)' && echo "OK: extras wired"
+
+# 4. CLI flag override
+/home/toph/code/tools/claudebox/result/bin/claudebox --cmd /usr/bin/env --dry-run --yes 2>&1 | tail -3
+
+# 5. Missing harness binary
+printf 'cmd = totally-nonexistent-binary-xyz\n' > .claudebox
+/home/toph/code/tools/claudebox/result/bin/claudebox --dry-run --yes 2>&1 | grep -i 'not found' && echo "OK: missing binary errors"
+
+# 6. --check reports config files
+rm .claudebox
+/home/toph/code/tools/claudebox/result/bin/claudebox --check 2>&1 | grep -E '\.claudebox(/config)?'
+
+cd - && rm -rf /tmp/le7-int
+```
+
+
+
+- `nix build .#claudebox` succeeds (shellcheck clean)
+- Default invocation unchanged: `claude --dangerously-skip-permissions` still launched when no config / no `--cmd`
+- `cmd = ` config or `--cmd ` launches that binary WITHOUT `--dangerously-skip-permissions`
+- Missing harness binary aborts with error+exit 1
+- `mount_home` entries become `--bind` in BWRAP_ARGS and dry-run; missing subdirs warn but do not abort
+- `path_add` entries are prepended to SANDBOX_PATH and visible in audit + dry-run
+- Cascade order respected: `~/.claudebox/config` loaded first, `/.claudebox` second; CLI flags applied last
+- Audit shows `=== Config ===` section listing loaded files, harness cmd, and accumulated arrays when relevant
+- `--check` reports presence/absence of both config file paths
+
+
+
diff --git a/.planning/quick/260505-le7-add-harness-config-file-support-to-claud/260505-le7-SUMMARY.md b/.planning/quick/260505-le7-add-harness-config-file-support-to-claud/260505-le7-SUMMARY.md
new file mode 100644
index 0000000..a158ea3
--- /dev/null
+++ b/.planning/quick/260505-le7-add-harness-config-file-support-to-claud/260505-le7-SUMMARY.md
@@ -0,0 +1,115 @@
+---
+phase: 260505-le7
+plan: 01
+subsystem: claudebox.sh
+tags: [config, harness, sandbox, cli-flags]
+dependency_graph:
+ requires: []
+ provides: [harness-config-support, mount-home, path-add, per-project-claudebox]
+ affects: [claudebox.sh]
+tech_stack:
+ added: []
+ patterns: [cascade-config-load, cli-override-pattern, IS_DEFAULT_CLAUDE-flag]
+key_files:
+ created: []
+ modified: [claudebox.sh]
+decisions:
+ - "CLI captures use CLI_HARNESS_CMD/CLI_MOUNT_HOME/CLI_PATH_ADD temporaries to allow config-then-CLI-override ordering despite flag parsing happening before CANONICAL_ROOT is available"
+ - "compute_canonical_root hoisted to near-top so it is available in --check mode and config loading"
+ - "SANDBOX_PATH mutated before ENV_ARGS is built so PATH_ADD shows in audit automatically"
+ - "IS_DEFAULT_CLAUDE boolean used to gate --dangerously-skip-permissions rather than string comparison on HARNESS_CMD"
+metrics:
+ duration: "~10min"
+ completed: "2026-05-05"
+ tasks: 2
+ files: 1
+---
+
+# Phase 260505-le7 Plan 01: Add Harness Config File Support to claudebox Summary
+
+Config file loading and alternate harness support for claudebox — users can pin `gsd` or any binary as the sandbox command via `~/.claudebox/config` or `/.claudebox`, with per-project home mounts and PATH augmentation.
+
+## Tasks Completed
+
+| Task | Name | Commit | Files |
+|------|------|--------|-------|
+| 1 | Parse config files and CLI flags; globals; load_config_file; HARNESS_BIN resolution | fbbb355 | claudebox.sh |
+| 2 | Wire HARNESS_BIN, MOUNT_HOME, PATH_ADD into SANDBOX_CMD, BWRAP_ARGS, ENV_ARGS, audit, dry-run | fbbb355 | claudebox.sh |
+
+Both tasks were implemented in a single commit since Task 2 is a direct continuation of the same function/variable initialisation started in Task 1.
+
+## What Was Built
+
+### Config file format
+
+Two config files are loaded in cascade order (later overrides scalar `cmd`; arrays `mount_home`/`path_add` accumulate):
+
+1. `~/.claudebox/config` (global)
+2. `/.claudebox` (per-project)
+
+Format: `KEY = VALUE` lines; blank and `#`-prefixed lines ignored.
+
+Supported keys:
+- `cmd` — alternate binary to launch inside sandbox (default: `claude`)
+- `mount_home` — subdir of `$HOME` to bind-mount read-write (can repeat)
+- `path_add` — dir to prepend to sandbox PATH (can repeat; `~` expanded)
+
+### CLI flags
+
+- `--cmd ` — overrides `cmd` from config
+- `--mount-home ` — appends to `mount_home` array
+- `--path-add ` — appends to `path_add` array
+
+CLI values are applied on top of config (config loaded first, CLI appended/overridden last).
+
+### Harness binary resolution
+
+- If `cmd` is set (via config or `--cmd`): resolved via `command -v`; missing binary → error + exit 1
+- Otherwise: defaults to `claude` binary from `runtimeInputs`
+- `IS_DEFAULT_CLAUDE` boolean gates `--dangerously-skip-permissions`: only added when launching the default claude binary
+
+### Ordering fix
+
+`compute_canonical_root` moved to near-top of script (before `--check` block) so it is available in both `--check` mode and config loading. Config files are loaded after `CANONICAL_ROOT` is computed (post-instance-dir setup). CLI captures (`CLI_*`) collected during flag parsing and merged after config loading.
+
+### Audit/dry-run integration
+
+- `=== Config ===` header shown when config files are loaded or non-default harness is active
+- `mount_home` entries appear in Mounts section (skipped if dir doesn't exist)
+- `path_add` entries appear at front of PATH in env section
+- `--dry-run` shows extra `--bind` lines for existing `mount_home` dirs
+- `--check` reports presence/absence of `~/.claudebox/config` and `/.claudebox`
+
+## Integration Test Results
+
+All 6 integration checks passed:
+
+1. Default invocation preserved: `claude --dangerously-skip-permissions` still the final exec when no config/flags
+2. `cmd = bash` in `.claudebox` → `bash` launched without `--dangerously-skip-permissions`
+3. `mount_home = .config` → `--bind $HOME/.config $HOME/.config` in dry-run (when dir exists)
+4. `path_add = ~/.local/bin` → `/home/toph/.local/bin` prepended to sandbox PATH
+5. `--cmd totally-nonexistent-binary-xyz` → "Error: configured cmd ... not found in PATH", exit 1
+6. `--check` reports `~/.claudebox/config` (WARN: not found) and `/.claudebox` (WARN: not found)
+
+## Deviations from Plan
+
+### Implementation note
+
+Tasks 1 and 2 were committed together in a single atomic commit (`fbbb355`) rather than two separate commits. The code flows were tightly coupled (Task 2 consumed globals initialized in Task 1, both in claudebox.sh) making separation artificial.
+
+No functional deviations. All must-have truths from the plan's frontmatter are satisfied.
+
+## Known Stubs
+
+None.
+
+## Threat Flags
+
+None. No new network endpoints, auth paths, file access patterns, or schema changes at trust boundaries. Config files are read-only host files already under user control; no privilege escalation path.
+
+## Self-Check: PASSED
+
+- claudebox.sh modified: FOUND
+- Commit fbbb355 exists: FOUND
+- `nix build .#claudebox` succeeded: PASSED (shellcheck clean)
+- All integration tests: PASSED