claudebox/.planning/phases/05-per-project-instance-isolation/05-CONTEXT.md

6.9 KiB

Phase 5: Per-Project Instance Isolation - Context

Gathered: 2026-04-10 (assumptions mode) Status: Ready for planning

## Phase Boundary

Each project directory gets its own isolated Claude state so conversation history, todos, and settings do not bleed between projects. Launching from a git worktree shares state with the main worktree of the same repo. Two concurrent sessions in the same project do not corrupt each other. claudebox --gc removes instance directories whose project root no longer exists on disk.

Credentials (OAuth tokens, API keys) are intentionally shared across all instances — not isolated per project.

## Implementation Decisions

Instance Isolation Mechanism

  • D-01: Use CLAUDE_CONFIG_DIR env var (via --setenv CLAUDE_CONFIG_DIR "$HOME/.claudebox/instances/<hash>") to redirect Claude Code's data directory to a per-project path. The existing ~/.claudebox bind mount stays intact — it is still needed for SANDBOX.md and CLAUDE.md management.
  • D-02: Instance directories live at ~/.claudebox/instances/<hash>/ on the host. The hash is derived from the canonical project root (see D-03).
  • D-03: ~/.claude.json (top-level auth) is hardcoded by Claude Code to $HOME and is NOT redirected by CLAUDE_CONFIG_DIR. If it exists on the host at $HOME/.claude.json, bind-mount it read-write into the sandbox alongside the existing credential mounts. If absent, skip silently (same pattern as Phase 4 credentials).

Project Root Identification

  • D-04: Canonical project root = git rev-parse --git-common-dir resolved to absolute path, falling back to $CWD for non-git directories. This gives all worktrees of the same repo the same instance key, satisfying the worktree-sharing requirement.
  • D-05: Hash the canonical root path with SHA-256 (or sha256sum, available via coreutils in the sandbox PATH) to produce the instance directory name. The hash is truncated to 16 hex chars for readability: ~/.claudebox/instances/a3f9c2b1d4e8f701/.

Credentials Stay Shared

  • D-06: ~/.claudebox/.credentials.json (OAuth tokens, Phase 4) and ~/.claude.json are bind-mounted from the host top-level into every sandbox — not per-instance. OAuth token refreshes must write to a single file shared across all sessions; per-instance copies would cause divergence and re-auth failures.

GC Mechanism

  • D-07: On instance creation, write a project-root plaintext file inside the instance dir containing the canonical project root path. Example: echo "$CANONICAL_ROOT" > "$INSTANCE_DIR/project-root".
  • D-08: claudebox --gc iterates ~/.claudebox/instances/*/project-root, reads each path, and removes any instance dir whose recorded path no longer exists on disk ([[ ! -d "$root" ]]). Print removed paths to stderr.
  • D-09: Add --gc to the case "$1" in flag-parsing block (line 9 of claudebox.sh) alongside --yes, --dry-run, --check.

Concurrent Session Safety

  • D-10: No locking mechanism needed. CLAUDE_CONFIG_DIR gives each session its own directory tree; concurrent sessions in the same project share the same instance dir but Claude Code itself manages file-level concurrency within its own data dir (same as it does without claudebox). If Claude Code corrupts its own state under concurrency, that is Claude Code's problem, not claudebox's.

Claude's Discretion

  • Exact hash truncation length (16 chars recommended above, can adjust)
  • Whether to print instance path at launch (could be a --verbose addition later)
  • Handling of ~/.claudebox/instances/ directory creation (ensure it exists before writing)

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

Codebase

  • claudebox.sh — full script; flag parsing (lines 1-18), BWRAP_ARGS construction (lines 365-401), credential mount logic (lines 104-122), SANDBOX.md/CLAUDE.md management (lines 124-175)
  • flake.nix — Nix packaging; runtimeInputs list (must include coreutils for sha256sum)

Requirements

  • .planning/REQUIREMENTS.md — v2 requirements section; INST-01 through INST-04 are NOT yet defined here (they need to be added during planning)
  • .planning/ROADMAP.md — Phase 5 success criteria (lines 44-53)

External (researched)

  • Claude Code env var CLAUDE_CONFIG_DIR: official docs confirm it redirects sessions/settings/todos; ~/.claude.json NOT redirected (hardcoded to $HOME)
  • Claude Code does NOT auto-resume on launch; --continue / --resume are explicit opt-in flags

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • Flag parsing block (lines 1-18 of claudebox.sh): add --gc case here using the existing pattern
  • BWRAP_ARGS array with += appends: add --setenv CLAUDE_CONFIG_DIR "..." using the same pattern as existing --setenv calls
  • Credential detection pattern (lines 104-122): same [[ -f "$FILE" ]] pattern for ~/.claude.json optional mount
  • coreutils is already in runtimeInputs (flake.nix) — provides sha256sum for hashing

Established Patterns

  • All sandbox flags are built into BWRAP_ARGS array before exec bwrap "${BWRAP_ARGS[@]}" — new --setenv CLAUDE_CONFIG_DIR follows the same pattern
  • Conditional mounts use if [[ "$VAR" == true ]]; then BWRAP_ARGS+=(...) — same pattern for ~/.claude.json
  • --dry-run block (lines 318-360) is a hardcoded parallel reproduction of BWRAP_ARGS — must also be updated to include CLAUDE_CONFIG_DIR and optional ~/.claude.json mount
  • print_audit() Mounts section must be updated to show the instance dir and ~/.claude.json if mounted

Integration Points

  • Line 383: --bind "$HOME/.claudebox" "$HOME/.claudebox" — stays unchanged; CLAUDE_CONFIG_DIR is additive
  • Line 384: --symlink "$HOME/.claudebox" "$HOME/.claude" — stays unchanged
  • Lines 366-368: env var --setenv block in BWRAP_ARGS — insert CLAUDE_CONFIG_DIR here
  • Lines 390-392: credential bind conditional — add parallel block for ~/.claude.json
  • Lines 333-360: dry-run echo block — add CLAUDE_CONFIG_DIR setenv echo + ~/.claude.json conditional echo

</code_context>

## Specific Ideas
  • Instance dirs at ~/.claudebox/instances/<16-char-hash>/ — human-debuggable length, not full 64-char SHA-256
  • GC prints removed paths to stderr (consistent with claudebox's stderr-only output convention)
  • --gc exits after running GC, does not launch Claude (same pattern as --check)
## Deferred Ideas
  • Per-instance settings.json override (project-specific model selection) — Phase 7 (Named Profiles)
  • --gc --dry-run to preview what would be removed — could be added but is Phase 5+ scope creep
  • claudebox --list-instances to show all instances with their project roots — nice-to-have, not required

None — analysis stayed within phase scope.


Phase: 05-per-project-instance-isolation Context gathered: 2026-04-10