# Phase 2: Env Audit and CLI Polish - Research **Researched:** 2026-04-09 **Domain:** Bash CLI UX -- flag parsing, ANSI formatting, TTY detection **Confidence:** HIGH ## Summary This phase adds pre-launch transparency and diagnostic CLI flags to the existing claudebox.sh script. All work is pure bash -- no new dependencies, no new Nix packages, no external tools. The existing code already has the data structures (ENV_ARGS, HOST_ALLOWLIST, CLAUDEBOX_EXTRA_ENV parsing) and the flag parsing skeleton (case/esac). The phase extends these with display logic, confirmation prompts, and two new early-exit modes (--dry-run, --check). The main technical concerns are: (1) correctly iterating ENV_ARGS for display without breaking the bwrap invocation, (2) ANSI escape code portability, (3) non-interactive stdin detection, and (4) shellcheck compliance since writeShellApplication enforces it at build time. **Primary recommendation:** Build parallel data structures for audit display rather than parsing the ENV_ARGS array. Track env vars in associative arrays by category during construction, then format for display separately from the bwrap --setenv pairs. ## User Constraints (from CONTEXT.md) ### Locked Decisions - **D-01:** Group env vars by source in three labeled sections: "Sandbox-generated", "Host (allowlisted)", "Extra (CLAUDEBOX_EXTRA_ENV)". Display to stderr. - **D-02:** PATH is split by `:` and displayed one entry per line (indented under PATH=) for readability. - **D-03:** Use plain ANSI escape codes for color/formatting -- no external dependency like gum. Bold section headers, colored section labels. - **D-04:** Auto-mask values where the variable name matches `*KEY*`, `*TOKEN*`, `*SECRET*`, `*PASSWORD*`, `*CREDENTIAL*` (case-insensitive). Show first 7 + last 4 characters with `...` in between. - **D-05:** Use `Proceed? [Y/n]` prompt -- default is proceed (Enter launches). User must type `n` or `no` to abort. - **D-06:** If stdin is not a TTY (piped input, CI, scripts), abort with error telling user to pass `--yes`/`-y`. Do NOT auto-proceed. - **D-07:** All audit output and the prompt go to stderr. Keeps stdout clean. - **D-08:** `--yes` / `-y` skips the env audit display and confirmation entirely -- launches immediately. - **D-09:** `--dry-run` prints the full bwrap command without executing. Claude's discretion on format. - **D-10:** `--check` verifies prerequisites: bwrap exists, required Nix packages available, `~/.claudebox` exists. Claude's discretion on depth/format. ### Claude's Discretion - `--dry-run` output format (single-line vs multiline, annotated vs raw) - `--check` diagnostic depth (existence-only vs version checks vs connectivity tests) - Exact ANSI color choices and spacing - Flag parsing order and error messages for invalid flag combinations ### Deferred Ideas (OUT OF SCOPE) None -- discussion stayed within phase scope. ## Phase Requirements | ID | Description | Research Support | |----|-------------|------------------| | UX-01 | Pre-launch env audit displays all env vars being passed into the sandbox on stderr | Audit display patterns, ANSI formatting, ENV_ARGS iteration approach | | UX-02 | Pre-launch env audit prompts for confirmation before proceeding | TTY detection, read prompt pattern, non-interactive abort | | UX-03 | `--yes` / `-y` flag skips the env audit confirmation | Flag parsing extension to existing case/esac | | UX-04 | `--dry-run` flag prints the full bwrap command without executing | Command reconstruction from arrays, printf %q quoting | | UX-05 | `--check` flag verifies bwrap exists, required Nix packages available, and ~/.claudebox exists | command -v checks, exit code conventions | ## Standard Stack No new packages required. Everything is bash builtins and existing runtimeInputs. | Tool | Source | Purpose | Already Available | |------|--------|---------|-------------------| | bash builtins | runtimeInputs | ANSI output, read, test, printf | Yes | | jq | runtimeInputs | Not needed for this phase | Yes (unused) | | coreutils | runtimeInputs | tput alternative -- but ANSI codes are simpler | Yes | ## Architecture Patterns ### Integration Points in claudebox.sh The current script flow is linear: ``` 1. Parse flags (--shell) 2. Resolve binaries 3. Record CWD, ensure ~/.claudebox 4. Generate gitconfig 5. Build ENV_ARGS array 6. Build SANDBOX_CMD 7. exec bwrap ``` Phase 2 inserts into this flow: ``` 1. Parse flags (--shell, --yes/-y, --dry-run, --check) [EXTEND] 2. --check: early exit [NEW] 3. Resolve binaries 4. Record CWD, ensure ~/.claudebox 5. Generate gitconfig 6. Build ENV_ARGS array 7. Build SANDBOX_CMD 8. Env audit display + confirmation (unless --yes) [NEW] 9. --dry-run: print command and exit [NEW] 10. exec bwrap ``` ### Pattern: Parallel Display Data Rather than parsing ENV_ARGS (which is `--setenv key value` triplets), maintain separate display-oriented arrays during construction. This avoids fragile parsing of the bwrap args array. ```bash # During env construction, also track for display declare -A SANDBOX_VARS # sandbox-generated vars declare -A HOST_VARS # host allowlisted vars declare -A EXTRA_VARS # CLAUDEBOX_EXTRA_ENV vars ``` [VERIFIED: reading claudebox.sh -- the three categories already have distinct code blocks that can populate these] ### Pattern: ANSI Escape Codes ```bash # Color constants -- define once at top BOLD=$'\033[1m' RESET=$'\033[0m' DIM=$'\033[2m' CYAN=$'\033[36m' YELLOW=$'\033[33m' GREEN=$'\033[32m' RED=$'\033[31m' ``` [ASSUMED] These are standard VT100/ECMA-48 sequences supported by all modern terminals. No tput dependency needed. ### Pattern: Value Masking (D-04) ```bash mask_value() { local name="$1" value="$2" # Case-insensitive match on var name if [[ "${name^^}" == *KEY* || "${name^^}" == *TOKEN* || "${name^^}" == *SECRET* || "${name^^}" == *PASSWORD* || "${name^^}" == *CREDENTIAL* ]]; then local len=${#value} if (( len > 11 )); then echo "${value:0:7}...${value: -4}" else echo "***" fi else echo "$value" fi } ``` Note: `${name^^}` converts to uppercase in bash 4+. NixOS ships bash 5.x, so this is safe. [VERIFIED: NixOS uses bash 5.x from nixpkgs] ### Pattern: PATH Display (D-02) ```bash display_path() { echo " PATH=" IFS=':' read -ra path_entries <<< "$1" for entry in "${path_entries[@]}"; do echo " $entry" done } ``` ### Pattern: TTY Detection (D-06) ```bash if [[ -t 0 ]]; then # Interactive -- show prompt read -r -p "Proceed? [Y/n] " response < /dev/tty # ... else echo "Error: stdin is not a terminal. Pass --yes or -y to skip confirmation." >&2 exit 1 fi ``` Important: Use `read < /dev/tty` rather than plain `read` because stdin may be consumed by pipes even when /dev/tty exists. The `[[ -t 0 ]]` check catches the non-interactive case. [ASSUMED] ### Pattern: --dry-run Output Recommend multiline format with one flag per line, matching the existing `exec bwrap` layout in the script. This makes it easy to diff against the actual invocation and spot issues. ```bash if [[ "$DRY_RUN" == true ]]; then echo "bwrap \\" >&2 echo " --clearenv \\" >&2 # ... each flag on its own line exit 0 fi ``` Use `printf '%q '` for values that may contain special characters (though in practice, env values and paths are clean). ### Pattern: --check Diagnostics Recommend checking: 1. `command -v bwrap` -- is bwrap on PATH 2. `command -v claude` -- is claude on PATH 3. Key runtimeInputs: git, curl, nix, bash 4. `~/.claudebox` directory exists 5. `ANTHROPIC_API_KEY` is set (warn if missing, don't fail) Output: one line per check with pass/fail indicator. Exit 0 if all required checks pass, exit 1 if any required check fails. ### Pattern: Flag Parsing Extension The existing parser uses `for arg in "$@"` with shift. Extend with additional cases. Important: `--check` should be checked first (early exit before env construction), but parsing order can collect all flags first, then branch. ```bash SKIP_AUDIT=false DRY_RUN=false CHECK_MODE=false SHELL_MODE=false CLAUDE_ARGS=() for arg in "$@"; do case "$arg" in --yes|-y) SKIP_AUDIT=true ;; --dry-run) DRY_RUN=true ;; --check) CHECK_MODE=true ;; --shell) SHELL_MODE=true ;; --) shift; break ;; *) CLAUDE_ARGS+=("$arg") ;; esac done ``` Note: Current parsing uses `shift` and `break` which is problematic for multi-flag support. The refactored approach collects all flags in one pass and stores remaining args in CLAUDE_ARGS. [VERIFIED: current claudebox.sh only handles --shell with shift+break] ### Anti-Patterns to Avoid - **Parsing ENV_ARGS for display:** The array contains `--setenv key value` triplets interleaved with bwrap flags. Iterating it for display is fragile. Track display data separately. - **Using `tput` for colors:** Adds an ncurses dependency. ANSI escape codes are sufficient and have no dependency. - **Auto-proceeding in non-interactive mode:** D-06 explicitly requires aborting. Don't silently proceed. - **Echoing sensitive values without masking:** D-04 requires masking KEY/TOKEN/SECRET/PASSWORD/CREDENTIAL patterns. ## Don't Hand-Roll | Problem | Don't Build | Use Instead | Why | |---------|-------------|-------------|-----| | Color output | ncurses/tput wrapper | Raw ANSI escapes | D-03 mandates plain ANSI, zero deps | | Argument parsing | getopt/getopts | Simple case/esac loop | Only 4 flags, no complex options, matches existing pattern | ## Common Pitfalls ### Pitfall 1: Shellcheck Violations **What goes wrong:** writeShellApplication runs shellcheck at build time. Bash-isms like `${name^^}` or `declare -A` may trigger warnings. **Why it happens:** shellcheck defaults may flag associative arrays or uppercase expansion. **How to avoid:** Use `# shellcheck disable=SCXXXX` directives only when necessary. Test with `shellcheck claudebox.sh` locally before `nix build`. Associative arrays and `${var^^}` are valid bash and shellcheck-clean in bash mode. **Warning signs:** `nix build` fails with shellcheck errors. ### Pitfall 2: Flag Parsing Breaking Passthrough **What goes wrong:** Claudebox consumes a flag meant for claude, or fails to pass remaining args. **Why it happens:** Current parsing uses shift+break which only handles one flag. **How to avoid:** Refactor to collect all known flags, accumulate unknown args in CLAUDE_ARGS array, pass CLAUDE_ARGS to claude. **Warning signs:** `claudebox --model sonnet` silently drops `--model`. ### Pitfall 3: read Prompt in Non-TTY **What goes wrong:** `read` hangs or reads garbage when stdin is piped. **Why it happens:** No TTY check before prompting. **How to avoid:** Check `[[ -t 0 ]]` before read. Read from `/dev/tty` explicitly. **Warning signs:** Script hangs in CI or when piped. ### Pitfall 4: Masking Short Values **What goes wrong:** Masking "first 7 + last 4" on a 5-character value reveals the whole thing. **Why it happens:** No length check before substring extraction. **How to avoid:** If value length <= 11, show `***` instead of partial mask. **Warning signs:** Short API keys fully visible in audit output. ### Pitfall 5: ANSI Codes in Redirected Output **What goes wrong:** If stderr is redirected to a file, ANSI escape codes pollute the output. **Why it happens:** Colors are sent regardless of terminal capability. **How to avoid:** Optional: check `[[ -t 2 ]]` and suppress colors if stderr is not a terminal. This is discretionary per D-03, but good practice. **Warning signs:** Garbled text in log files. ## Code Examples ### Complete Flag Parsing (recommended) ```bash # Parse claudebox flags -- collect our flags, pass the rest to claude SKIP_AUDIT=false DRY_RUN=false CHECK_MODE=false SHELL_MODE=false CLAUDE_ARGS=() while (( $# > 0 )); do case "$1" in --yes|-y) SKIP_AUDIT=true ;; --dry-run) DRY_RUN=true ;; --check) CHECK_MODE=true ;; --shell) SHELL_MODE=true ;; --) shift; CLAUDE_ARGS+=("$@"); break ;; *) CLAUDE_ARGS+=("$1") ;; esac shift done ``` [ASSUMED] This is standard bash argument parsing. The `while/shift` pattern is more robust than the current `for/shift/break`. ### Env Audit Display Function ```bash print_audit() { local bold=$'\033[1m' reset=$'\033[0m' local cyan=$'\033[36m' yellow=$'\033[33m' green=$'\033[32m' echo "${bold}${cyan}=== Sandbox Environment ===${reset}" >&2 echo "" >&2 echo "${bold}Sandbox-generated:${reset}" >&2 for var in HOME USER PATH SHELL TMPDIR XDG_RUNTIME_DIR NIX_SSL_CERT_FILE SSL_CERT_FILE; do if [[ "$var" == "PATH" ]]; then echo " ${green}PATH=${reset}" >&2 IFS=':' read -ra entries <<< "$SANDBOX_PATH" for entry in "${entries[@]}"; do echo " $entry" >&2 done else # Look up value from the sandbox vars echo " ${green}${var}=${reset}${SANDBOX_DISPLAY[$var]}" >&2 fi done # ... similar for host and extra sections } ``` ### --check Implementation ```bash run_check() { local pass=true local green=$'\033[32m' red=$'\033[31m' reset=$'\033[0m' check_cmd() { if command -v "$1" &>/dev/null; then echo "${green}OK${reset} $1" >&2 else echo "${red}FAIL${reset} $1 -- not found" >&2 pass=false fi } echo "claudebox prerequisites:" >&2 check_cmd bwrap check_cmd claude check_cmd git check_cmd nix if [[ -d "$HOME/.claudebox" ]]; then echo "${green}OK${reset} ~/.claudebox exists" >&2 else echo "${red}FAIL${reset} ~/.claudebox -- not found (will be created on first run)" >&2 fi if [[ -v ANTHROPIC_API_KEY ]]; then echo "${green}OK${reset} ANTHROPIC_API_KEY is set" >&2 else echo "${yellow}WARN${reset} ANTHROPIC_API_KEY is not set" >&2 fi if [[ "$pass" == true ]]; then exit 0 else exit 1 fi } ``` ## Assumptions Log | # | Claim | Section | Risk if Wrong | |---|-------|---------|---------------| | A1 | ANSI VT100 escape codes work in all target terminals | Architecture Patterns | Low -- NixOS terminals universally support ANSI | | A2 | `read < /dev/tty` is the correct pattern for prompting when stdin may be piped | Architecture Patterns | Low -- standard Unix practice | | A3 | `${var^^}` uppercase expansion is shellcheck-clean | Pitfalls | Low -- shellcheck knows bash, would only flag if shell directive is sh | | A4 | while/shift is more robust than for/shift/break for multi-flag parsing | Code Examples | Very low -- well-established pattern | ## Open Questions 1. **Should `--dry-run` also show the env audit?** - What we know: D-09 says print the full bwrap command. The env audit is separate (D-01). - What's unclear: Whether `--dry-run` implies `--yes` (skip audit) or shows audit then command. - Recommendation: `--dry-run` implies `--yes` -- the user wants to see the command, not be prompted. Show the command and exit. 2. **Should `--check` be combinable with other flags?** - What we know: `--check` is an early-exit diagnostic. - What's unclear: What if user passes `--check --dry-run`. - Recommendation: `--check` takes priority, exits before other flags matter. No need to error on combinations. 3. **Color disable for non-terminal stderr?** - What we know: D-03 says use ANSI codes. Doesn't mention disabling. - What's unclear: Whether to add `NO_COLOR` or `[[ -t 2 ]]` detection. - Recommendation: Respect the `NO_COLOR` env var convention if set. Otherwise, always emit ANSI. This is low-effort and follows modern CLI conventions. ## Sources ### Primary (HIGH confidence) - `claudebox.sh` -- current implementation, read directly - `flake.nix` -- current derivation structure, read directly - `02-CONTEXT.md` -- locked decisions D-01 through D-10 - `REQUIREMENTS.md` -- UX-01 through UX-05 definitions ### Secondary (MEDIUM confidence) - Bash reference manual (training data) -- builtins, parameter expansion, test operators - VT100/ECMA-48 escape codes (training data) -- ANSI color sequences ## Metadata **Confidence breakdown:** - Standard stack: HIGH -- no new packages, pure bash - Architecture: HIGH -- clear insertion points in existing script, straightforward patterns - Pitfalls: HIGH -- well-known bash gotchas, verified against existing code **Research date:** 2026-04-09 **Valid until:** No expiry -- bash and ANSI codes are stable