diff --git a/.planning/phases/05-per-project-instance-isolation/05-RESEARCH.md b/.planning/phases/05-per-project-instance-isolation/05-RESEARCH.md
new file mode 100644
index 0000000..d061ae6
--- /dev/null
+++ b/.planning/phases/05-per-project-instance-isolation/05-RESEARCH.md
@@ -0,0 +1,403 @@
+# Phase 5: Per-Project Instance Isolation - Research
+
+**Researched:** 2026-04-13
+**Domain:** bash scripting, bubblewrap mounts, Claude Code storage layout
+**Confidence:** HIGH
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+
+- **D-01:** Replace `--bind ~/.claudebox ~/.claudebox` + `--symlink ~/.claudebox ~/.claude` with `--bind ~/.claude ~/.claude`
+- **D-02:** Overlay `--bind ~/.claudebox/projects ~/.claude/projects` AFTER the `~/.claude` bind mount (bwrap last-mount-wins). Isolates per-project memory while keeping everything else from real `~/.claude`.
+- **D-03:** Overlay `--bind ~/.claudebox/history.jsonl ~/.claude/history.jsonl` AFTER the `~/.claude` bind mount. Isolates session prompt history.
+- **D-04:** Ensure `~/.claudebox/projects/` directory and `~/.claudebox/history.jsonl` file exist at startup (before bwrap).
+- **D-05:** `.credentials.json` handling stays as-is — already separate in current code.
+- **D-06:** SANDBOX.md and CLAUDE.md management moves from `~/.claudebox/` to `~/.claude/` approach — must not overwrite user's real `~/.claude/CLAUDE.md` destructively.
+- **D-07:** `~/.claudebox/projects/` contains per-project subdirs keyed by hash: `~/.claudebox/projects/<16-char-hash>/`. Inside sandbox, these appear at `~/.claude/projects//`.
+- **D-08:** Canonical project root = `git rev-parse --git-common-dir` resolved to absolute path, falling back to `$CWD` for non-git directories. All worktrees of the same repo get the same hash.
+- **D-09:** Hash = SHA-256 of canonical root path, truncated to 16 hex chars.
+- **D-10:** On instance creation, write `project-root` plaintext file inside `~/.claudebox/projects//` containing the canonical root path.
+- **D-11:** `claudebox --gc` iterates `~/.claudebox/projects/*/project-root`, reads each path, removes any dir whose recorded path no longer exists on disk. Prints removed paths to stderr.
+- **D-12:** Add `--gc` to flag-parsing block alongside `--yes`, `--dry-run`, `--check`.
+- **D-13:** No locking needed. Claude Code manages file-level concurrency within its own data dir.
+- **D-14:** `CLAUDE_CONFIG_DIR` env var approach is abandoned. Direct mount + overlay is simpler.
+
+### Claude's Discretion
+
+- Exact hash truncation length (16 chars recommended)
+- Whether to print instance path at launch (verbose addition later)
+- SANDBOX.md strategy: whether to keep writing it to `~/.claudebox/` and bind-mount it, or write to `~/.claude/` directly
+- Whether `history.jsonl` needs per-project hashing too or one shared sandbox history is fine
+
+### Deferred Ideas (OUT OF SCOPE)
+
+- Per-instance `settings.json` override (project-specific model selection) — Phase 7
+- `--gc --dry-run` to preview what would be removed
+- `claudebox --list-instances` to show all instances with their project roots
+
+
+
+## Phase Requirements
+
+INST-01 through INST-04 are NOT yet defined in REQUIREMENTS.md. The CONTEXT.md says they need to be added during planning. The planner should define them based on the Phase 5 success criteria and add them to REQUIREMENTS.md.
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| INST-01 | Each project directory has isolated conversation history (no cross-contamination between projects) | D-02 overlay mounts `~/.claudebox/projects//` as `~/.claude/projects/`; Claude Code writes per-project subdirs inside |
+| INST-02 | Git worktrees of the same repo share instance state with the main worktree | D-08: `git rev-parse --git-common-dir` resolved to absolute gives same root for all worktrees |
+| INST-03 | Two concurrent claudebox sessions in the same project do not corrupt each other's state | D-13: No locking needed; Claude Code handles its own file-level concurrency |
+| INST-04 | `claudebox --gc` removes instance directories for project roots that no longer exist on disk | D-11/D-12: iterate `project-root` files, remove stale dirs |
+
+There is also an implicit "plugin fix" requirement (call it PLUG-01 or scope it as part of INST-01): the real `~/.claude/` being mounted means all plugins, skills, hooks, MCP configs, commands, agents, keybindings, and settings.json become visible inside the sandbox. The planner should decide whether to track this separately.
+
+
+## Summary
+
+Phase 5 is two coordinated changes: (1) fix the plugin mount architecture so that all Claude Code config files in `~/.claude/` are visible inside the sandbox, and (2) implement per-project isolation so that conversation history and project memory are scoped to the canonical git root.
+
+The current architecture mounts `~/.claudebox` at `~/.claudebox` and symlinks it to `~/.claude` inside the sandbox. This means any Claude Code config that lives in `~/.claude/` (plugins, skills, hooks, mcp.json, etc.) is invisible — the sandbox only sees `~/.claudebox/` contents. The fix is direct: mount `~/.claude` at `~/.claude`, then overlay only the two paths that need isolation (`projects/` and `history.jsonl`) with content from `~/.claudebox/`.
+
+Per-project isolation works by mounting `~/.claudebox/projects/<16-char-hash>/` as the entire `~/.claude/projects/` view inside the sandbox. Claude Code writes conversation history and project memory into `~/.claude/projects/`, which under the overlay lands in the project-specific hash dir. Two different projects get different hash dirs, so their histories never mix. Git worktrees of the same repo resolve to the same canonical root (via `git rev-parse --git-common-dir`), so they share the same hash dir.
+
+**Primary recommendation:** Follow decisions D-01 through D-14 exactly as written. All are verified technically sound by live testing in this environment. The only ambiguity is the SANDBOX.md injection strategy (see Discretion section below).
+
+## Standard Stack
+
+### Core
+
+| Tool | Version | Purpose | Why Standard |
+|------|---------|---------|--------------|
+| `bubblewrap` (bwrap) | 0.9.x (nixpkgs) | Sandbox + filesystem isolation | Already in use; verified `--bind` overlay behavior confirmed via live test [VERIFIED: live bwrap test] |
+| `sha256sum` (coreutils) | GNU coreutils 9.10 | Hash canonical root path | Already in `runtimeInputs`; `printf '%s' "$path" | sha256sum | cut -c1-16` produces stable 16-char hex [VERIFIED: live test] |
+| `git` | nixpkgs | Resolve canonical root for worktrees | Already in `runtimeInputs`; `git rev-parse --git-common-dir` returns `.git` (relative) for normal repos, absolute path for worktrees [VERIFIED: live test] |
+| `readlink -f` | coreutils | Resolve relative `.git` to absolute path | Already used in claudebox.sh for NixOS symlink resolution |
+
+No new packages needed. All required tools are already in `runtimeInputs` in `flake.nix`.
+
+**Installation:** No changes to `flake.nix` required.
+
+## Architecture Patterns
+
+### Recommended Project Structure (on host filesystem)
+
+```
+~/.claudebox/
+├── .credentials.json # OAuth tokens (hard-linked to ~/.claude/.credentials.json)
+├── SANDBOX.md # Managed by claudebox, injected as overlay
+├── CLAUDE.md # Managed by claudebox (kept for backward compat, not used)
+├── history.jsonl # Sandbox-side prompt history (overlays ~/.claude/history.jsonl)
+└── projects/
+ ├── 2458cbe666750168/ # SHA-256[:16] of /home/user/code/myproject
+ │ ├── project-root # plaintext: /home/user/code/myproject
+ │ └── -home-user-code-myproject/ # Claude Code writes here (path-based name)
+ │ ├── *.jsonl # conversation history
+ │ └── memory/ # project memory
+ └── 421ebd2f76562141/ # SHA-256[:16] of /home/user/code/other
+ ├── project-root
+ └── -home-user-code-other/
+```
+
+### Mount Order (critical)
+
+```
+--bind ~/.claude ~/.claude
+ (makes all real ~/.claude/ content visible)
+--bind ~/.claudebox/projects// ~/.claude/projects/
+ (overlays projects/ with this project's isolated dir)
+--bind ~/.claudebox/history.jsonl ~/.claude/history.jsonl
+ (overlays history.jsonl with sandbox-side file)
+--bind ~/.claudebox/SANDBOX.md ~/.claude/SANDBOX.md
+ (injects SANDBOX.md without touching user's CLAUDE.md)
+--bind ~/.claudebox/.credentials.json ~/.claude/.credentials.json
+ (overlays credentials with claudebox-managed copy)
+```
+
+**Last-mount-wins is bwrap's documented behavior.** Verified with live bwrap tests: both subdirectory overlays and file-level overlays work correctly. [VERIFIED: live bwrap tests]
+
+### Pattern 1: Canonical Root Computation
+
+```bash
+# Source: live testing + D-08 design
+compute_canonical_root() {
+ local cwd="$1"
+ local git_common
+ git_common=$(git -C "$cwd" rev-parse --git-common-dir 2>/dev/null) || {
+ echo "$cwd"
+ return
+ }
+ # Make absolute if relative (normal repo returns ".git")
+ if [[ "$git_common" != /* ]]; then
+ git_common="$cwd/$git_common"
+ fi
+ dirname "$(readlink -f "$git_common")"
+}
+
+CANONICAL_ROOT=$(compute_canonical_root "$CWD")
+INSTANCE_HASH=$(printf '%s' "$CANONICAL_ROOT" | sha256sum | cut -c1-16)
+INSTANCE_DIR="$HOME/.claudebox/projects/$INSTANCE_HASH"
+```
+
+**Why `--git-common-dir` and not `--show-toplevel`:** In a worktree, `--show-toplevel` returns the worktree's own root (not the main repo root). `--git-common-dir` always points to the main repo's `.git` directory, so all worktrees of the same repo get the same canonical root. [VERIFIED: git docs + live testing]
+
+### Pattern 2: Instance Initialization
+
+```bash
+# Source: D-04, D-10
+mkdir -p "$INSTANCE_DIR"
+HISTORY_FILE="$HOME/.claudebox/history.jsonl"
+touch "$HISTORY_FILE"
+
+# Write project-root only on first creation (idempotent)
+if [[ ! -f "$INSTANCE_DIR/project-root" ]]; then
+ printf '%s\n' "$CANONICAL_ROOT" > "$INSTANCE_DIR/project-root"
+fi
+```
+
+**Why `touch` for history.jsonl:** bwrap bind mount requires the source path to exist. Attempting to bind-mount a non-existent file produces `bwrap: Can't find source path: No such file or directory` and exits 1. [VERIFIED: live bwrap test]
+
+### Pattern 3: GC Implementation
+
+```bash
+# Source: D-11, D-12
+gc_instances() {
+ local removed=0
+ for dir in "$HOME/.claudebox/projects"/*/; do
+ [[ -d "$dir" ]] || continue
+ local root_file="$dir/project-root"
+ [[ -f "$root_file" ]] || continue # skip dirs without tracking file (defensive)
+ local root_path
+ root_path=$(< "$root_file")
+ if [[ ! -d "$root_path" ]]; then
+ rm -rf "$dir"
+ echo "Removed: $dir (project path gone: $root_path)" >&2
+ (( removed++ )) || true
+ fi
+ done
+ echo "GC complete: $removed instance(s) removed." >&2
+}
+```
+
+### Pattern 4: Credential Mount Target Update
+
+```bash
+# OLD (current, with symlink architecture):
+# --bind "$HOME/.claudebox/.credentials.json" "$HOME/.claudebox/.credentials.json"
+# (visible as ~/.claude/.credentials.json via the symlink)
+
+# NEW (with direct ~/.claude bind):
+# --bind "$HOME/.claudebox/.credentials.json" "$HOME/.claude/.credentials.json"
+# (explicit overlay after the ~/.claude bind mount)
+```
+
+Note: on this system `~/.claude/.credentials.json` and `~/.claudebox/.credentials.json` are the same inode (hard links from Phase 4). The overlay still has the correct behavior: it re-mounts the claudebox-managed credential file on top of whatever `~/.claude/.credentials.json` the direct bind exposed. [VERIFIED: inode check]
+
+### Anti-Patterns to Avoid
+
+- **Mounting entire `~/.claudebox/projects/` as `~/.claude/projects/`:** This would expose ALL project dirs to every project, defeating isolation. Mount only the per-project hash subdir.
+- **Using `git rev-parse --show-toplevel` instead of `--git-common-dir`:** `--show-toplevel` returns the worktree root, not the main repo root. Worktrees would get different hashes than the main checkout.
+- **Skipping `touch ~/.claudebox/history.jsonl`:** bwrap fails with a hard error if the source doesn't exist. The script would crash on first run.
+- **Writing to `~/.claude/CLAUDE.md`:** With the new mount architecture, `~/.claude` is the user's real config dir. Writing CLAUDE.md there would destructively modify the user's actual config. Use bind-mounted SANDBOX.md instead.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Content-addressed storage | Custom hash scheme | `sha256sum \| cut -c1-16` | Already in coreutils; stable, collision-resistant for path strings |
+| Filesystem overlay/union | Custom copy-on-write | bwrap `--bind` last-mount-wins | Kernel-native, no performance cost, no FUSE dependencies |
+| File locking for concurrent sessions | Custom lockfile scheme | Nothing (D-13) | Claude Code already manages its own concurrency within its data dir |
+
+## Common Pitfalls
+
+### Pitfall 1: bwrap requires source to exist before launch
+
+**What goes wrong:** Adding `--bind ~/.claudebox/history.jsonl ~/.claude/history.jsonl` without first ensuring the source file exists. bwrap exits 1 with "Can't find source path".
+**Why it happens:** bwrap performs bind mounts at namespace creation time; the source must be a real, existing path on the host filesystem.
+**How to avoid:** `touch "$HOME/.claudebox/history.jsonl"` before the bwrap call (already in D-04).
+**Also applies to:** The per-project hash dir: `mkdir -p "$INSTANCE_DIR"` must happen before bwrap.
+
+### Pitfall 2: Overlay mount order matters
+
+**What goes wrong:** Placing the `~/.claude/projects/` overlay bind BEFORE the `~/.claude` bind. The `~/.claude` bind would then overwrite the overlay (last-mount-wins goes the wrong way).
+**Why it happens:** bwrap processes `--bind` args left to right; the last bind for any given path wins.
+**How to avoid:** Always: `--bind ~/.claude ~/.claude` FIRST, then overlays.
+
+### Pitfall 3: Credential mount target path not updated
+
+**What goes wrong:** Keeping `--bind "$CREDS_FILE" "$HOME/.claudebox/.credentials.json"` after removing the `~/.claudebox` bind and symlink. The target path no longer exists in the sandbox (no `~/.claudebox/` dir is mounted).
+**How to avoid:** Change target to `"$HOME/.claude/.credentials.json"`.
+
+### Pitfall 4: CLAUDE.md injection modifies user's real config
+
+**What goes wrong:** Keeping the current CLAUDE.md injection logic (lines 167-174) that writes to `"$HOME/.claudebox/CLAUDE.md"`. After the mount change, `~/.claudebox/CLAUDE.md` is a host-side file not visible in the sandbox at all. Worse, if the code is updated to write to `~/.claude/CLAUDE.md`, it destructively prepends `@SANDBOX.md` to the user's real config.
+**How to avoid:** Mount SANDBOX.md as a single-file overlay (`--bind ~/.claudebox/SANDBOX.md ~/.claude/SANDBOX.md`). The user's real `~/.claude/CLAUDE.md` already contains `@SANDBOX.md` on this system.
+**Warning sign:** If SANDBOX.md content is missing inside the sandbox after the migration.
+
+### Pitfall 5: Dry-run block not mirrored
+
+**What goes wrong:** Updating `BWRAP_ARGS` but forgetting the `--dry-run` echo block (lines 319-361). The dry-run output shows the old mount layout.
+**How to avoid:** The dry-run block must be updated in sync with BWRAP_ARGS. Both blocks need: new `~/.claude` bind, projects overlay, history overlay, SANDBOX.md overlay, updated credentials target.
+
+### Pitfall 6: Unstaged CLAUDE_JSON changes from Phase 4
+
+**What goes wrong:** The working tree has uncommitted changes adding `CLAUDE_JSON_FILE` detection and a `--bind $CLAUDE_JSON_FILE $HOME/.claude.json` mount (the `~/.claude.json` file, separate from the `~/.claude/` directory). These changes are not in the last commit.
+**Context:** `~/.claude.json` is at `$HOME/.claude.json` (not inside `~/.claude/`), so this mount is independent of the Phase 5 architecture changes. The `CLAUDE_JSON_FILE` mount should be incorporated into Phase 5 work since it was left uncommitted from Phase 4.
+
+### Pitfall 7: Glob on empty projects/ directory
+
+**What goes wrong:** The GC loop `for dir in "$HOME/.claudebox/projects"/*/; do` — if `projects/` is empty, bash expands the glob literally and the loop runs once with a non-existent path.
+**How to avoid:** Add `[[ -d "$dir" ]] || continue` as the first statement in the loop (already in the Pattern 3 example above).
+
+## Code Examples
+
+### Complete instance initialization block
+
+```bash
+# Source: D-04, D-08, D-09, D-10
+
+# Compute canonical project root (worktree-aware)
+CWD=$(pwd)
+GIT_COMMON=$(git -C "$CWD" rev-parse --git-common-dir 2>/dev/null) || true
+if [[ -n "$GIT_COMMON" ]]; then
+ [[ "$GIT_COMMON" != /* ]] && GIT_COMMON="$CWD/$GIT_COMMON"
+ CANONICAL_ROOT=$(dirname "$(readlink -f "$GIT_COMMON")")
+else
+ CANONICAL_ROOT="$CWD"
+fi
+
+INSTANCE_HASH=$(printf '%s' "$CANONICAL_ROOT" | sha256sum | cut -c1-16)
+INSTANCE_DIR="$HOME/.claudebox/projects/$INSTANCE_HASH"
+
+# Create instance dir and project-root file
+mkdir -p "$INSTANCE_DIR"
+if [[ ! -f "$INSTANCE_DIR/project-root" ]]; then
+ printf '%s\n' "$CANONICAL_ROOT" > "$INSTANCE_DIR/project-root"
+fi
+
+# Ensure history.jsonl source exists (bwrap requires it)
+touch "$HOME/.claudebox/history.jsonl"
+```
+
+### Relevant BWRAP_ARGS section (after change)
+
+```bash
+# Source: D-01, D-02, D-03, D-05, D-06 (after Phase 5 changes)
+BWRAP_ARGS=(
+ # ... clearenv, ENV_ARGS, tmpfs, proc, dev, nix, etc ...
+ --tmpfs "$HOME"
+ # Phase 5: mount real ~/.claude (replaces ~/.claudebox bind + symlink)
+ --bind "$HOME/.claude" "$HOME/.claude"
+ # Phase 5: overlay projects/ with this project's isolated dir
+ --bind "$INSTANCE_DIR" "$HOME/.claude/projects"
+ # Phase 5: overlay history.jsonl with sandbox-side file
+ --bind "$HOME/.claudebox/history.jsonl" "$HOME/.claude/history.jsonl"
+ # Phase 5: inject SANDBOX.md as file overlay
+ --bind "$HOME/.claudebox/SANDBOX.md" "$HOME/.claude/SANDBOX.md"
+)
+# Credentials overlay (mount target changes from .claudebox to .claude)
+if [[ "$CREDS_MOUNT" == true ]]; then
+ BWRAP_ARGS+=(--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json")
+fi
+# CLAUDE_JSON mount (independent of ~/.claude/ dir)
+if [[ "$CLAUDE_JSON_MOUNT" == true ]]; then
+ BWRAP_ARGS+=(--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json")
+fi
+```
+
+### Flag parsing addition for --gc
+
+```bash
+# Source: D-12 (add to existing while/case block)
+GC_MODE=false
+
+while (( $# > 0 )); do
+ case "$1" in
+ --yes|-y) SKIP_AUDIT=true ;;
+ --dry-run) DRY_RUN=true ;;
+ --check) CHECK_MODE=true ;;
+ --shell) SHELL_MODE=true ;;
+ --gc) GC_MODE=true ;; # NEW
+ --) shift; CLAUDE_ARGS+=("$@"); break ;;
+ *) CLAUDE_ARGS+=("$1") ;;
+ esac
+ shift
+done
+```
+
+## Open Questions
+
+1. **SANDBOX.md injection strategy**
+ - What we know: User's real `~/.claude/CLAUDE.md` already has `@SANDBOX.md` on this system. The CONTEXT.md says D-06 "must not overwrite user's real `~/.claude/CLAUDE.md` destructively".
+ - What's unclear: Should claudebox continue to manage SANDBOX.md content (write it to `~/.claudebox/SANDBOX.md` and bind-mount it), or stop injecting it entirely since the user's CLAUDE.md already has `@SANDBOX.md`?
+ - Recommendation: Continue writing `~/.claudebox/SANDBOX.md` and bind-mounting it as `~/.claude/SANDBOX.md`. This keeps the content claudebox-controlled without touching the user's CLAUDE.md. Remove the CLAUDE.md injection logic (lines 167-174) entirely — the real `~/.claude/CLAUDE.md` already has `@SANDBOX.md`, and we must not touch it.
+
+2. **Uncommitted CLAUDE_JSON_FILE changes**
+ - What we know: The working tree has uncommitted changes (not in HEAD) adding CLAUDE_JSON mount for `~/.claude.json` (the root-level file, not inside `~/.claude/`). Phase 4 verification passed without this code.
+ - What's unclear: Was this intentionally left uncommitted, or is it Phase 4 work that needs to be committed first?
+ - Recommendation: Incorporate these changes into Phase 5. The `~/.claude.json` mount is independent of Phase 5 architecture changes and is useful (stores auth tokens). The planner should include a task to commit these changes as part of Phase 5 work.
+
+3. **`~/.claudebox/projects/` overlay vs subdir mount**
+ - This was clarified during research: the design mounts `~/.claudebox/projects//` as `~/.claude/projects/` (the per-project subdir AS the entire projects view), not `~/.claudebox/projects/` as `~/.claude/projects/` (which would expose all projects).
+ - Claude Code creates path-based subdirs (e.g., `-home-user-code-myproject/`) INSIDE the mounted projects dir, which lands inside the hash subdir on the host.
+
+## Environment Availability
+
+| Dependency | Required By | Available | Version | Fallback |
+|------------|-------------|-----------|---------|----------|
+| `sha256sum` (coreutils) | D-09 hash computation | yes | GNU coreutils 9.10 | — |
+| `git` | D-08 canonical root | yes | nixpkgs git | — |
+| `readlink -f` (coreutils) | D-08 absolute path | yes | GNU coreutils 9.10 | — |
+| `bwrap` subdirectory overlay | D-02 projects isolation | yes | 0.9.x (nixpkgs) | — |
+| `bwrap` file overlay | D-03 history isolation | yes | 0.9.x (nixpkgs) | — |
+
+All dependencies available. No new packages required in `flake.nix`.
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| `~/.claudebox` bind + symlink to `~/.claude` | Direct `~/.claude` bind + overlays for isolation paths | Phase 5 (this) | All plugins/skills/hooks become visible in sandbox |
+| No per-project isolation | Hash-based per-project instance dirs | Phase 5 (this) | Conversation history scoped to git root |
+| No GC | `--gc` flag removes stale instance dirs | Phase 5 (this) | Prevents unbounded growth of `~/.claudebox/projects/` |
+
+**Deprecated/outdated:**
+- `--symlink ~/.claudebox ~/.claude` in BWRAP_ARGS: replaced by direct `--bind ~/.claude ~/.claude`
+- CLAUDE.md injection logic (lines 167-174): no longer needed; user's real CLAUDE.md already has `@SANDBOX.md`
+- Target path `~/.claudebox/.credentials.json` for credentials overlay: changes to `~/.claude/.credentials.json`
+
+## Assumptions Log
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | Claude Code manages its own file-level concurrency within its data dir (D-13) | Architecture Patterns | If wrong, concurrent sessions in same project could corrupt data; but since D-13 is a locked decision, implementation proceeds without locking |
+| A2 | User's `~/.claude/CLAUDE.md` already contains `@SANDBOX.md` on all deployments | Open Questions | SANDBOX.md content wouldn't be injected if user doesn't have `@SANDBOX.md` in their CLAUDE.md; mitigated by the SANDBOX.md bind-mount overlay |
+
+**All other claims in this research were verified via live testing or direct inspection of the codebase.**
+
+## Sources
+
+### Primary (HIGH confidence)
+- Live bwrap testing — `--bind` overlay behavior, subdirectory overlay under bound parent, file overlay, behavior when source doesn't exist
+- `claude --help` output — confirmed no `--data-dir` flag exists; Claude Code v2.1.97
+- `~/.claude/projects/` directory inspection — confirmed path-based naming convention (e.g., `-home-toph-code-tools-claudebox/`)
+- `~/.claude/history.jsonl` content inspection — confirmed it stores prompt display history with project path references
+- Inode inspection — confirmed `~/.claude/.credentials.json` and `~/.claudebox/.credentials.json` are the same inode (hard links)
+- `sha256sum` availability — confirmed in coreutils 9.10 already in `runtimeInputs`
+- `git rev-parse --git-common-dir` behavior — confirmed `.git` (relative) for normal repos, resolves correctly via `readlink -f`
+- claudebox.sh source (working tree) — read completely; all line numbers referenced in CONTEXT.md verified
+- flake.nix — confirmed `runtimeInputs`; no new packages needed
+
+### Secondary (MEDIUM confidence)
+- PLUGIN_MOUNT_FIX.md — architectural rationale document confirming the plugin visibility problem and overlay fix
+- CONTEXT.md decisions D-01 through D-14 — user-validated design decisions from discuss-phase
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — all tools verified live on this system
+- Architecture: HIGH — bwrap overlay behavior verified with live tests; Claude Code storage layout confirmed by inspection
+- Pitfalls: HIGH — most pitfalls discovered via direct testing or code inspection, not assumption
+
+**Research date:** 2026-04-13
+**Valid until:** 2026-07-13 (stable APIs; Claude Code storage format could change on any release)