diff --git a/.devcontainer/.env.example b/.devcontainer/.env.example index 7e51083..1533e10 100644 --- a/.devcontainer/.env.example +++ b/.devcontainer/.env.example @@ -1,9 +1,8 @@ # CodeForge Environment Configuration # Copy to .env and customize. .env is gitignored. -# Paths -CLAUDE_CONFIG_DIR=/workspaces/.claude -# CONFIG_SOURCE_DIR is derived from script location; uncomment to override: +# Paths (defaults shown — uncomment to override) +# CLAUDE_CONFIG_DIR=$HOME/.claude # CONFIG_SOURCE_DIR=/custom/path/to/config # Setup: copy config files to CLAUDE_CONFIG_DIR (per config/file-manifest.json) diff --git a/.devcontainer/.secrets.example b/.devcontainer/.secrets.example index eedf89e..122ddba 100644 --- a/.devcontainer/.secrets.example +++ b/.devcontainer/.secrets.example @@ -10,3 +10,6 @@ GH_EMAIL= # NPM auth token for registry.npmjs.org NPM_TOKEN= + +# Claude long-lived auth token (from 'claude setup-token') +CLAUDE_AUTH_TOKEN= diff --git a/.devcontainer/CHANGELOG.md b/.devcontainer/CHANGELOG.md index a6161e3..8e779af 100644 --- a/.devcontainer/CHANGELOG.md +++ b/.devcontainer/CHANGELOG.md @@ -9,9 +9,37 @@ ### Changed +#### Configuration +- Moved `.claude` directory from `/workspaces/.claude` to `~/.claude` (home directory) +- Added Docker named volume for persistence across rebuilds (per-instance isolation via `${devcontainerId}`) +- `CLAUDE_CONFIG_DIR` now defaults to `~/.claude` + +#### Authentication +- Added `CLAUDE_AUTH_TOKEN` support in `.secrets` for long-lived tokens from `claude setup-token` +- Auto-creates `.credentials.json` from token on container start (skips if already exists) +- Added `CLAUDE_AUTH_TOKEN` to devcontainer.json secrets declaration + +#### Security +- Protected-files-guard now blocks modifications to `.credentials.json` +- Replaced `eval` tilde expansion with `getent passwd` lookup across all scripts (prevents shell injection via `SUDO_USER`/`USER`) +- Auth token value is now JSON-escaped before writing to `.credentials.json` +- Credential directory created with restrictive umask (700) matching credential file permissions (600) + #### Status Bar - **ccstatusline line 1** — distinct background colors for each token widget (blue=input, magenta=output, yellow=cached, green=total), bold 2-char labels (In, Ou, Ca, Tt) fused to data widgets, `rawValue: true` on model widget to strip "Model:" prefix, restored spacing between token segments +#### Scripts +- Replaced `setup-symlink-claude.sh` with `setup-migrate-claude.sh` (one-time migration) +- Auto-migrates from `/workspaces/.claude/` if `.credentials.json` present +- `chown` in mcp-qdrant poststart hooks now uses resolved `_USERNAME` instead of hardcoded `vscode` or `$(id -un)` +- **Migration script hardened** — switched from `cp -rn` to `cp -a` (archive mode); added marker-based idempotency, critical file verification, ownership fixup, and old-directory rename +- **`.env` deprecation guard** — `setup.sh` detects stale `CLAUDE_CONFIG_DIR=/workspaces/.claude` in `.env`, overrides to `$HOME/.claude`, and auto-comments the line on disk + +#### Documentation +- All docs now reference `~/.claude` as default config path +- Added `CLAUDE_AUTH_TOKEN` setup flow to README, configuration reference, and troubleshooting +- ccstatusline README verification commands now respect `CLAUDE_CONFIG_DIR` + ### Fixed #### Plugin Marketplace @@ -29,6 +57,9 @@ ### Removed +#### Scripts +- `setup-symlink-claude.sh` — no longer needed with native home directory location + #### VS Code Extensions - **Todo+** (`fabiospampinato.vscode-todo-plus`) — removed from devcontainer extensions diff --git a/.devcontainer/CLAUDE.md b/.devcontainer/CLAUDE.md index fd2a04a..59a4c52 100644 --- a/.devcontainer/CLAUDE.md +++ b/.devcontainer/CLAUDE.md @@ -31,7 +31,7 @@ CodeForge devcontainer for AI-assisted development with Claude Code. | `devcontainer.json` | Container definition: image, features, mounts | | `.env` | Boolean flags controlling setup steps | -Config files deploy via `file-manifest.json` on every container start. Most deploy to `/workspaces/.claude/`; ccstatusline config deploys to `~/.config/ccstatusline/`. Each entry supports `overwrite`: `"if-changed"` (default, sha256), `"always"`, or `"never"`. Supported variables: `${CLAUDE_CONFIG_DIR}`, `${WORKSPACE_ROOT}`, `${HOME}`. +Config files deploy via `file-manifest.json` on every container start. Most deploy to `~/.claude/`; ccstatusline config deploys to `~/.config/ccstatusline/`. Each entry supports `overwrite`: `"if-changed"` (default, sha256), `"always"`, or `"never"`. Supported variables: `${CLAUDE_CONFIG_DIR}`, `${WORKSPACE_ROOT}`, `${HOME}`. ## Commands @@ -76,7 +76,8 @@ Rules in `config/defaults/rules/` deploy to `.claude/rules/` on every container | Variable | Value | |----------|-------| -| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | +| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | +| `CLAUDE_AUTH_TOKEN` | Long-lived token from `claude setup-token` (optional, via `.secrets` or Codespaces secrets) | | `ANTHROPIC_MODEL` | `claude-opus-4-6` | | `WORKSPACE_ROOT` | `/workspaces` | | `TERM` | `${localEnv:TERM:xterm-256color}` (via `remoteEnv` — forwards host TERM, falls back to 256-color) | @@ -84,6 +85,12 @@ Rules in `config/defaults/rules/` deploy to `.claude/rules/` on every container All experimental feature flags are in `settings.json` under `env`. Setup steps controlled by boolean flags in `.env`. +## Authentication & Persistence + +The `~/.claude/` directory is backed by a Docker named volume (`codeforge-claude-config-${devcontainerId}`), persisting config, credentials, and session data across container rebuilds. Each devcontainer instance gets an isolated volume. + +**Token authentication:** Set `CLAUDE_AUTH_TOKEN` in `.devcontainer/.secrets` (or as a Codespaces secret) with a long-lived token from `claude setup-token`. On container start, `setup-auth.sh` auto-creates `~/.claude/.credentials.json` with `600` permissions. If `.credentials.json` already exists, token injection is skipped (idempotent). Tokens must match `sk-ant-*` format. + ## Modifying Behavior 1. **Change model**: Edit `config/defaults/settings.json` → `"model"` field diff --git a/.devcontainer/README.md b/.devcontainer/README.md index ff2cb9b..c7c45d2 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -40,7 +40,20 @@ Get an API key from [console.anthropic.com](https://console.anthropic.com/). ### Credential Persistence -Authentication credentials are stored in `/workspaces/.claude/` and persist across container rebuilds. +Authentication credentials are stored in `~/.claude/` and persist across container rebuilds via a Docker named volume. + +### Long-Lived Token Authentication + +For headless or automated environments, you can use a long-lived auth token instead of browser login: + +1. Generate a token: `claude setup-token` +2. Add to `.devcontainer/.secrets`: + ```bash + CLAUDE_AUTH_TOKEN=sk-ant-oat01-your-token-here + ``` +3. On next container start, `setup-auth.sh` will create `~/.claude/.credentials.json` automatically. + +You can also set `CLAUDE_AUTH_TOKEN` as a Codespaces secret for cloud environments. For more options, see the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code). @@ -111,7 +124,7 @@ Expected output shows your authenticated account and token scopes. ### Credential Persistence -GitHub CLI credentials are automatically persisted across container rebuilds. The container is configured to store credentials in `/workspaces/.gh/` (via `GH_CONFIG_DIR`), which is part of the bind-mounted workspace. +GitHub CLI credentials are automatically persisted across container rebuilds. The container is configured to store credentials in `/workspaces/.gh/` (via `GH_CONFIG_DIR`), which is part of the bind-mounted workspace. Claude Code credentials persist via a Docker named volume mounted at `~/.claude/`. **You only need to authenticate once.** After running `gh auth login` or configuring `.secrets`, your credentials will survive container rebuilds and be available in future sessions. @@ -199,7 +212,7 @@ Copy `.devcontainer/.env.example` to `.devcontainer/.env` and customize: | Variable | Default | Description | |----------|---------|-------------| -| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Claude configuration directory | +| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Claude configuration directory | | `SETUP_CONFIG` | `true` | Copy config files during setup (per `file-manifest.json`) | | `SETUP_ALIASES` | `true` | Add `cc`/`claude`/`ccraw` aliases to shell | | `SETUP_AUTH` | `true` | Configure Git/NPM auth from `.secrets` | @@ -301,6 +314,8 @@ Three methods for providing GitHub/NPM credentials, in order of precedence: All methods persist across container rebuilds via the bind-mounted `/workspaces/.gh/` directory. +4. **`.secrets` file with `CLAUDE_AUTH_TOKEN`** — Long-lived Claude auth token from `claude setup-token`. Auto-creates `~/.claude/.credentials.json` on container start. + ## Agents & Skills Agents and skills are distributed across focused plugins (replacing the former `code-directive` monolith). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 27226fd..ae37aff 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,9 +5,17 @@ "workspaceFolder": "/workspaces", "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces,type=bind", + "mounts": [ + { + "source": "codeforge-claude-config-${devcontainerId}", + "target": "/home/vscode/.claude", + "type": "volume" + } + ], + "remoteEnv": { "WORKSPACE_ROOT": "/workspaces", - "CLAUDE_CONFIG_DIR": "/workspaces/.claude", + "CLAUDE_CONFIG_DIR": "/home/vscode/.claude", "GH_CONFIG_DIR": "/workspaces/.gh", "TMPDIR": "/workspaces/.tmp", "TERM": "${localEnv:TERM:xterm-256color}", @@ -29,6 +37,10 @@ }, "GH_EMAIL": { "description": "GitHub email for git config (optional)" + }, + "CLAUDE_AUTH_TOKEN": { + "description": "Claude long-lived auth token from 'claude setup-token' (optional - sk-ant-oat01-*)", + "documentationUrl": "https://docs.anthropic.com/en/docs/claude-code/cli-reference#claude-setup-token" } }, diff --git a/.devcontainer/docs/configuration-reference.md b/.devcontainer/docs/configuration-reference.md index df8994f..e8e8b94 100644 --- a/.devcontainer/docs/configuration-reference.md +++ b/.devcontainer/docs/configuration-reference.md @@ -23,7 +23,7 @@ These control what `setup.sh` does on each container start. Copy `.env.example` | Variable | Default | Description | |----------|---------|-------------| -| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Where Claude Code config files are stored | +| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Where Claude Code config files are stored | | `CONFIG_SOURCE_DIR` | `(auto-detected)` | Source directory for config defaults | | `SETUP_CONFIG` | `true` | Copy config files per `file-manifest.json` | | `SETUP_ALIASES` | `true` | Add cc/claude/ccraw/cc-tools aliases to shell | @@ -42,7 +42,7 @@ These environment variables are set in every terminal session inside the contain | Variable | Value | Description | |----------|-------|-------------| | `WORKSPACE_ROOT` | `/workspaces` | Workspace root directory | -| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Claude Code config directory | +| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Claude Code config directory | | `GH_CONFIG_DIR` | `/workspaces/.gh` | GitHub CLI config directory | | `TMPDIR` | `/workspaces/.tmp` | Temporary files directory | | `CLAUDECODE` | `null` (unset) | Unsets the variable to allow nested Claude Code sessions (claude-in-claude) | @@ -88,6 +88,9 @@ GH_TOKEN=ghp_your_token_here GH_USERNAME=your-github-username GH_EMAIL=your-email@example.com NPM_TOKEN=npm_your_token_here +CLAUDE_AUTH_TOKEN=sk-ant-oat01-your-token-here ``` +The `CLAUDE_AUTH_TOKEN` is a long-lived token from `claude setup-token`. When set, `setup-auth.sh` creates `~/.claude/.credentials.json` on container start (skips if already exists). + Environment variables with the same names take precedence over `.secrets` file values (useful for Codespaces). diff --git a/.devcontainer/docs/keybindings.md b/.devcontainer/docs/keybindings.md index 8cc2346..33a3c68 100644 --- a/.devcontainer/docs/keybindings.md +++ b/.devcontainer/docs/keybindings.md @@ -78,7 +78,7 @@ Edit `config/defaults/keybindings.json` to remap Claude Code actions to non-conf } ``` -The keybindings file is copied to `/workspaces/.claude/keybindings.json` on container start (controlled by `file-manifest.json`). +The keybindings file is copied to `~/.claude/keybindings.json` on container start (controlled by `file-manifest.json`). ## Claude Code Keybinding Reference diff --git a/.devcontainer/docs/troubleshooting.md b/.devcontainer/docs/troubleshooting.md index fc45179..815b6c4 100644 --- a/.devcontainer/docs/troubleshooting.md +++ b/.devcontainer/docs/troubleshooting.md @@ -32,6 +32,11 @@ Common issues and solutions for the CodeForge devcontainer. - Or configure `.devcontainer/.secrets` with `GH_TOKEN` for automatic auth on container start. - Credentials persist in `/workspaces/.gh/` across rebuilds. +**Problem**: Claude auth token not taking effect in Codespaces. + +- When `CLAUDE_AUTH_TOKEN` is set via Codespaces secrets, it persists as an environment variable for the entire container lifetime. The `unset` in `setup-auth.sh` only clears it in the child process. This is a Codespaces platform limitation. +- If `.credentials.json` already exists, the token injection is skipped (idempotent). Delete `~/.claude/.credentials.json` to force re-creation from the token. + **Problem**: Git push fails with permission error. - Run `gh auth status` to verify authentication. @@ -119,7 +124,7 @@ Common issues and solutions for the CodeForge devcontainer. ## How to Reset to Defaults -1. **Reset config files**: Delete `/workspaces/.claude/` and restart the container. `setup-config.sh` will recopy all files from `config/defaults/`. +1. **Reset config files**: Delete `~/.claude/` and restart the container. `setup-config.sh` will recopy all files from `config/defaults/`. 2. **Reset aliases**: Delete the `# Claude Code environment and aliases` block from `~/.bashrc` and `~/.zshrc`, then run `bash /workspaces/.devcontainer/scripts/setup-aliases.sh`. diff --git a/.devcontainer/features/ccstatusline/README.md b/.devcontainer/features/ccstatusline/README.md index 0975809..2722d0d 100644 --- a/.devcontainer/features/ccstatusline/README.md +++ b/.devcontainer/features/ccstatusline/README.md @@ -45,7 +45,7 @@ All widgets connected with powerline arrows (monokai theme). - **ccstatusline npm package**: Installed on-demand via `npx` (not globally) - **Configuration file**: `~/.config/ccstatusline/settings.json` with powerline theme -- **Claude Code integration**: Automatically updates `.claude/settings.json` +- **Claude Code integration**: Automatically updates `~/.claude/settings.json` - **Disk Usage**: Minimal (~2MB when cached by npx) ## Requirements @@ -75,9 +75,10 @@ The feature will validate these are present and exit with an error if missing. - ✅ **Session Resume**: Copyable `cc --resume {sessionId}` command via custom-command widget - ✅ **Burn Rate Tracking**: Live ccburn compact output showing pace indicators (🧊/🔥/🚨) - ✅ **ANSI Colors**: High-contrast colors optimized for dark terminals -- ✅ **Automatic Integration**: Auto-configures `.claude/settings.json` +- ✅ **Automatic Integration**: Auto-configures `~/.claude/settings.json` - ✅ **Idempotent**: Safe to run multiple times - ✅ **Multi-user**: Automatically detects container user +- ✅ **Config-aware**: Respects `CLAUDE_CONFIG_DIR` environment variable (defaults to `~/.claude`) ## Post-Installation Steps @@ -85,7 +86,7 @@ The feature will validate these are present and exit with an error if missing. This feature automatically: 1. Creates `~/.config/ccstatusline/settings.json` with powerline configuration -2. Configures `.claude/settings.json` to use ccstatusline +2. Configures `~/.claude/settings.json` to use ccstatusline **No manual steps required!** @@ -105,7 +106,7 @@ You should see formatted output with powerline styling. **3. Check Claude Code integration:** ```bash -cat /workspaces/.claude/settings.json | jq '.statusLine' +cat "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" | jq '.statusLine' ``` Should show: @@ -204,7 +205,7 @@ cat ~/.config/ccstatusline/settings.json | jq . echo '{"model":{"display_name":"Test"}}' | npx -y ccstatusline@latest # 3. Check Claude Code settings -cat /workspaces/.claude/settings.json | jq '.statusLine' +cat "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" | jq '.statusLine' # 4. Manually run auto-config if needed configure-ccstatusline-auto @@ -258,7 +259,7 @@ configure-ccstatusline-auto npm install -g ccstatusline@latest ``` -Then update `.claude/settings.json`: +Then update `${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json`: ```json { "statusLine": { diff --git a/.devcontainer/features/ccstatusline/install.sh b/.devcontainer/features/ccstatusline/install.sh index 0d16f01..f9cecb8 100755 --- a/.devcontainer/features/ccstatusline/install.sh +++ b/.devcontainer/features/ccstatusline/install.sh @@ -190,9 +190,11 @@ if ! command -v jq &>/dev/null; then exit 1 fi -SETTINGS_FILE="${WORKSPACE_ROOT:-/workspaces}/.claude/settings.json" # Use SUDO_USER since _REMOTE_USER isn't set in post-start hooks USERNAME="${SUDO_USER:-vscode}" +_USER_HOME=$(getent passwd "$USERNAME" 2>/dev/null | cut -d: -f6) +_USER_HOME="${_USER_HOME:-/home/$USERNAME}" +SETTINGS_FILE="${CLAUDE_CONFIG_DIR:-${_USER_HOME}/.claude}/settings.json" # Ensure directory exists mkdir -p "$(dirname "${SETTINGS_FILE}")" diff --git a/.devcontainer/features/claude-session-dashboard/README.md b/.devcontainer/features/claude-session-dashboard/README.md index 0a7a7a2..34512f4 100644 --- a/.devcontainer/features/claude-session-dashboard/README.md +++ b/.devcontainer/features/claude-session-dashboard/README.md @@ -33,8 +33,8 @@ claude-dashboard -p 8080 claude-dashboard --help ``` -The dashboard reads session data from `~/.claude/projects/` (symlinked to `/workspaces/.claude/projects/` in this devcontainer). +The dashboard reads session data from `~/.claude/projects/`. ## How persistence works -Dashboard settings and cache are stored at `~/.claude-dashboard/`. Since the home directory is ephemeral in devcontainers, a poststart hook symlinks `~/.claude-dashboard` → `/workspaces/.claude-dashboard/`, which is bind-mounted and survives rebuilds. +Dashboard settings and cache are stored at `~/.claude-dashboard/`. A poststart hook symlinks `~/.claude-dashboard` → `/workspaces/.claude-dashboard/`, which is bind-mounted and survives rebuilds. diff --git a/.devcontainer/features/mcp-qdrant/CHANGES.md b/.devcontainer/features/mcp-qdrant/CHANGES.md index 5230361..821e5ef 100644 --- a/.devcontainer/features/mcp-qdrant/CHANGES.md +++ b/.devcontainer/features/mcp-qdrant/CHANGES.md @@ -259,11 +259,11 @@ MCP_CONFIG_DIR="${USER_HOME}/.config/mcp" **Current Behavior:** - Feature creates: `~/.config/mcp/qdrant-config.json` -- Helper script (`configure-qdrant-mcp`) can update: `/workspaces/.claude/settings.json` +- Helper script (`configure-qdrant-mcp`) can update: `~/.claude/settings.json` - User must manually run helper script **Not Implemented (by request):** -- Automatic injection into `/workspaces/.claude/settings.json` during installation +- Automatic injection into `~/.claude/settings.json` during installation - This will be discussed separately --- @@ -378,7 +378,7 @@ Based on comprehensive review, the following fixes were applied: 2. ✅ Fixed credentials leak - Added cleanup trap, secure temp file handling ### High Priority Fixes -3. ✅ Removed unused config directory (~/.config/mcp) - Target is /workspaces/.claude/settings.json +3. ✅ Removed unused config directory (~/.config/mcp) - Target is ~/.claude/settings.json 4. ✅ Consolidated helper scripts - Removed duplicate manual helper, kept auto-config only 5. ✅ Fixed redundant redirections - Changed `&>/dev/null 2>&1` to `&>/dev/null` 6. ✅ Fixed hardcoded workspace paths - Now uses `${WORKSPACE_ROOT:-/workspaces}` diff --git a/.devcontainer/features/mcp-qdrant/README.md b/.devcontainer/features/mcp-qdrant/README.md index c3d2986..a56920b 100644 --- a/.devcontainer/features/mcp-qdrant/README.md +++ b/.devcontainer/features/mcp-qdrant/README.md @@ -154,6 +154,7 @@ The feature will validate these are present and exit with an error if missing. - ✅ **Cloud or Local**: Supports both Qdrant Cloud and local instances - ✅ **Idempotent**: Safe to run multiple times - ✅ **Multi-user**: Automatically detects container user +- ✅ **Config-aware**: Respects `CLAUDE_CONFIG_DIR` environment variable (defaults to `~/.claude`) - ✅ **Native mcpServers**: Uses VS Code's native devcontainer mcpServers support (declarative configuration) - ✅ **Dynamic Configuration**: Environment variables loaded from `/workspaces/.qdrant-mcp.env` file - ✅ **Secure**: API keys protected with 600 permissions on env file diff --git a/.devcontainer/features/mcp-qdrant/install.sh b/.devcontainer/features/mcp-qdrant/install.sh index aadd44c..06f03b7 100755 --- a/.devcontainer/features/mcp-qdrant/install.sh +++ b/.devcontainer/features/mcp-qdrant/install.sh @@ -188,8 +188,13 @@ else QDRANT_LOCAL_PATH="${QDRANT_LOCAL_PATH:-/workspaces/.qdrant/storage}" fi +# Resolve target user's home (guards against $HOME=/root during feature install) +_USERNAME="${SUDO_USER:-${USER:-vscode}}" +_USER_HOME=$(getent passwd "$_USERNAME" 2>/dev/null | cut -d: -f6) +_USER_HOME="${_USER_HOME:-/home/$_USERNAME}" + # Ensure settings.json exists -SETTINGS_FILE="/workspaces/.claude/settings.json" +SETTINGS_FILE="${CLAUDE_CONFIG_DIR:-${_USER_HOME}/.claude}/settings.json" if [ ! -f "$SETTINGS_FILE" ]; then echo "[mcp-qdrant] ERROR: $SETTINGS_FILE not found" exit 1 @@ -257,7 +262,7 @@ fi # Set proper permissions chmod 644 "$SETTINGS_FILE" -chown "$(id -un):$(id -gn)" "$SETTINGS_FILE" 2>/dev/null || true +chown "${_USERNAME}:${_USERNAME}" "$SETTINGS_FILE" 2>/dev/null || true echo "[mcp-qdrant] ✓ Configuration complete" HOOK_EOF diff --git a/.devcontainer/features/mcp-qdrant/poststart-hook.sh b/.devcontainer/features/mcp-qdrant/poststart-hook.sh index c42cf96..e754b19 100755 --- a/.devcontainer/features/mcp-qdrant/poststart-hook.sh +++ b/.devcontainer/features/mcp-qdrant/poststart-hook.sh @@ -56,8 +56,13 @@ else QDRANT_LOCAL_PATH="${QDRANT_LOCAL_PATH:-/workspaces/.qdrant/storage}" fi +# Resolve target user's home (guards against $HOME=/root when hook runs as root) +_USERNAME="${SUDO_USER:-${USER:-vscode}}" +_USER_HOME=$(getent passwd "$_USERNAME" 2>/dev/null | cut -d: -f6) +_USER_HOME="${_USER_HOME:-/home/$_USERNAME}" + # Ensure settings.json exists -SETTINGS_FILE="/workspaces/.claude/settings.json" +SETTINGS_FILE="${CLAUDE_CONFIG_DIR:-${_USER_HOME}/.claude}/settings.json" if [ ! -f "$SETTINGS_FILE" ]; then echo "[mcp-qdrant] ERROR: $SETTINGS_FILE not found" exit 1 @@ -125,6 +130,6 @@ fi # Set proper permissions chmod 644 "$SETTINGS_FILE" -chown vscode:vscode "$SETTINGS_FILE" 2>/dev/null || true +chown "${_USERNAME}:${_USERNAME}" "$SETTINGS_FILE" 2>/dev/null || true echo "[mcp-qdrant] ✓ Configuration complete" diff --git a/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected-bash.py b/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected-bash.py index d630e2d..0c74255 100644 --- a/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected-bash.py +++ b/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected-bash.py @@ -36,7 +36,7 @@ (r"\.crt$", "Blocked: .crt certificate files should not be edited directly"), (r"\.p12$", "Blocked: .p12 files contain sensitive cryptographic material"), (r"\.pfx$", "Blocked: .pfx files contain sensitive cryptographic material"), - (r"(^|/)credentials\.json$", "Blocked: credentials.json contains secrets"), + (r"(^|/)\.?credentials\.json$", "Blocked: credentials.json contains secrets"), (r"(^|/)secrets\.yaml$", "Blocked: secrets.yaml contains secrets"), (r"(^|/)secrets\.yml$", "Blocked: secrets.yml contains secrets"), (r"(^|/)secrets\.json$", "Blocked: secrets.json contains secrets"), diff --git a/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py b/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py index eb521d0..6fb2ef3 100644 --- a/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +++ b/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py @@ -40,7 +40,7 @@ (r"\.p12$", "Blocked: .p12 files contain sensitive cryptographic material"), (r"\.pfx$", "Blocked: .pfx files contain sensitive cryptographic material"), # Credential files - (r"(^|/)credentials\.json$", "Blocked: credentials.json contains secrets"), + (r"(^|/)\.?credentials\.json$", "Blocked: credentials.json contains secrets"), (r"(^|/)secrets\.yaml$", "Blocked: secrets.yaml contains secrets"), (r"(^|/)secrets\.yml$", "Blocked: secrets.yml contains secrets"), (r"(^|/)secrets\.json$", "Blocked: secrets.json contains secrets"), diff --git a/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/README.md b/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/README.md index 9013b1f..26bf983 100644 --- a/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/README.md +++ b/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/README.md @@ -27,7 +27,7 @@ These paths are always permitted regardless of working directory: | Path | Reason | |------|--------| -| `/workspaces/.claude/` | Claude config, plans, rules | +| `~/.claude/` | Claude config, plans, rules | | `/tmp/` | System temp directory | ### CWD Context Injection diff --git a/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py b/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py index 2c95080..a6bda4f 100755 --- a/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py +++ b/.devcontainer/plugins/devs-marketplace/plugins/workspace-scope-guard/scripts/guard-workspace-scope.py @@ -28,8 +28,9 @@ ] # Paths always allowed regardless of working directory +_home = os.environ.get("HOME", "/home/vscode") ALLOWED_PREFIXES = [ - "/workspaces/.claude/", # Claude config, plans, rules + f"{_home}/.claude/", # Claude config, plans, rules "/tmp/", # System scratch ] diff --git a/.devcontainer/scripts/check-setup.sh b/.devcontainer/scripts/check-setup.sh index 34e51cc..f13e458 100644 --- a/.devcontainer/scripts/check-setup.sh +++ b/.devcontainer/scripts/check-setup.sh @@ -36,8 +36,8 @@ echo "Core:" check "Claude Code installed" "command -v claude" warn_check "Claude native binary" "[ -x ~/.local/bin/claude ] || [ -x /usr/local/bin/claude ]" check "cc alias configured" "grep -q 'alias cc=' ~/.bashrc 2>/dev/null || grep -q 'alias cc=' ~/.zshrc 2>/dev/null" -check "Config directory exists" "[ -d '${CLAUDE_CONFIG_DIR:-/workspaces/.claude}' ]" -check "Settings file exists" "[ -f '${CLAUDE_CONFIG_DIR:-/workspaces/.claude}/settings.json' ]" +check "Config directory exists" "[ -d '${CLAUDE_CONFIG_DIR:-$HOME/.claude}' ]" +check "Settings file exists" "[ -f '${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json' ]" echo "" echo "Authentication:" diff --git a/.devcontainer/scripts/setup-auth.sh b/.devcontainer/scripts/setup-auth.sh index 7df38d1..e0dc36e 100755 --- a/.devcontainer/scripts/setup-auth.sh +++ b/.devcontainer/scripts/setup-auth.sh @@ -65,6 +65,50 @@ else echo "[setup-auth] NPM_TOKEN not set, skipping NPM auth" fi +# --- Claude auth token (from 'claude setup-token') --- +# Long-lived tokens only — generated via: claude setup-token +# Note: After unset, the token remains visible in /proc//environ for the +# lifetime of this process. This is a platform limitation of environment variables. +_USERNAME="${SUDO_USER:-${USER:-vscode}}" +_USER_HOME=$(getent passwd "$_USERNAME" 2>/dev/null | cut -d: -f6) +_USER_HOME="${_USER_HOME:-/home/$_USERNAME}" +CLAUDE_CRED_DIR="${CLAUDE_CONFIG_DIR:-${_USER_HOME}/.claude}" +CLAUDE_CRED_FILE="$CLAUDE_CRED_DIR/.credentials.json" +if [ -n "$CLAUDE_AUTH_TOKEN" ]; then + # Validate token format (claude setup-token produces sk-ant-* tokens) + if [[ ! "$CLAUDE_AUTH_TOKEN" =~ ^sk-ant- ]]; then + echo "[setup-auth] WARNING: CLAUDE_AUTH_TOKEN doesn't match expected format (sk-ant-*), skipping" + elif [ -f "$CLAUDE_CRED_FILE" ]; then + echo "[setup-auth] .credentials.json already exists, skipping token injection" + # Verify permissions haven't been tampered with + perms=$(stat -c %a "$CLAUDE_CRED_FILE" 2>/dev/null) + if [ -n "$perms" ] && [ "$perms" != "600" ]; then + echo "[setup-auth] WARNING: .credentials.json has permissions $perms (expected 600), fixing" + chmod 600 "$CLAUDE_CRED_FILE" + fi + AUTH_CONFIGURED=true + else + echo "[setup-auth] Creating .credentials.json from CLAUDE_AUTH_TOKEN..." + # Create directory with restrictive permissions (matches credential file at 600) + ( umask 077; mkdir -p "$CLAUDE_CRED_DIR" ) + # Escape JSON-special characters in token value (defense against malformed JSON + # if a token ever contains " or \ — unlikely with sk-ant-* but closes the gap) + ESCAPED_TOKEN=$(printf '%s' "$CLAUDE_AUTH_TOKEN" | sed 's/\\/\\\\/g; s/"/\\"/g') + # Write credentials with restrictive permissions from the start (no race window). + # Uses printf '%s' to avoid shell expansion of token value (defense against + # metacharacters in the token string — backticks, $(), quotes). + if ( umask 077; printf '{\n "claudeAiOauth": {\n "accessToken": "%s",\n "refreshToken": "%s",\n "expiresAt": 9999999999999,\n "scopes": ["user:inference", "user:profile"]\n }\n}\n' "$ESCAPED_TOKEN" "$ESCAPED_TOKEN" > "$CLAUDE_CRED_FILE" ); then + echo "[setup-auth] Claude auth token configured" + AUTH_CONFIGURED=true + else + echo "[setup-auth] WARNING: Failed to write .credentials.json — check permissions on $CLAUDE_CRED_DIR" + fi + fi + unset CLAUDE_AUTH_TOKEN +else + echo "[setup-auth] CLAUDE_AUTH_TOKEN not set, skipping Claude auth" +fi + # --- Summary --- if [ "$AUTH_CONFIGURED" = true ]; then echo "[setup-auth] Auth configuration complete" diff --git a/.devcontainer/scripts/setup-migrate-claude.sh b/.devcontainer/scripts/setup-migrate-claude.sh new file mode 100755 index 0000000..51acb3a --- /dev/null +++ b/.devcontainer/scripts/setup-migrate-claude.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# One-time migration: /workspaces/.claude → $HOME/.claude +# Migrates config, credentials, and rules from the old bind-mount location +# to the new home directory (Docker named volume). +# +# Uses cp -a (archive) for a faithful copy that preserves permissions, +# timestamps, symlinks, and directory structure. Migration is one-time +# (marker-gated), so overwrite is safe — the old directory is authoritative. + +OLD_DIR="/workspaces/.claude" +_USERNAME="${SUDO_USER:-${USER:-vscode}}" +_USER_HOME=$(getent passwd "$_USERNAME" 2>/dev/null | cut -d: -f6) +_USER_HOME="${_USER_HOME:-/home/$_USERNAME}" +NEW_DIR="${CLAUDE_CONFIG_DIR:-${_USER_HOME}/.claude}" +MARKER="$NEW_DIR/.migrated-from-workspaces" + +# Nothing to migrate if old directory doesn't exist +if [ ! -d "$OLD_DIR" ]; then + exit 0 +fi + +# Skip if old directory is empty (nothing worth migrating) +if [ -z "$(ls -A "$OLD_DIR" 2>/dev/null)" ]; then + exit 0 +fi + +# Idempotency: skip if migration already completed +if [ -f "$MARKER" ]; then + exit 0 +fi + +# Symlink protection: verify OLD_DIR itself is a real directory, not a symlink +if [ -L "$OLD_DIR" ]; then + echo "[setup-migrate] WARNING: /workspaces/.claude is a symlink, skipping migration for safety" + exit 0 +fi + +echo "[setup-migrate] Migrating /workspaces/.claude → $NEW_DIR ..." +mkdir -p "$NEW_DIR" + +# -a: archive mode (-dR --preserve=all) — preserves permissions, timestamps, +# symlinks, ownership, and directory structure faithfully. +# Errors logged explicitly (no 2>/dev/null) so failures are visible. +if cp -a "$OLD_DIR/." "$NEW_DIR/"; then + # Fix ownership — source files may be owned by a different uid from a + # previous container lifecycle. chown everything to the current user. + chown -R "$(id -un):$(id -gn)" "$NEW_DIR/" 2>/dev/null || true + + # Verify critical files arrived + MISSING="" + [ ! -f "$NEW_DIR/.claude.json" ] && [ -f "$OLD_DIR/.claude.json" ] && MISSING="$MISSING .claude.json" + [ ! -d "$NEW_DIR/plugins" ] && [ -d "$OLD_DIR/plugins" ] && MISSING="$MISSING plugins/" + [ ! -f "$NEW_DIR/.credentials.json" ] && [ -f "$OLD_DIR/.credentials.json" ] && MISSING="$MISSING .credentials.json" + if [ -n "$MISSING" ]; then + echo "[setup-migrate] WARNING: Migration incomplete — missing:$MISSING" + echo "[setup-migrate] Old directory preserved at $OLD_DIR for manual recovery" + exit 1 + fi + + # Mark migration complete + date -Iseconds > "$MARKER" + + # Rename old directory to .bak + if mv "$OLD_DIR" "${OLD_DIR}.bak" 2>/dev/null; then + echo "[setup-migrate] Migration complete. Old directory moved to ${OLD_DIR}.bak" + else + echo "[setup-migrate] Migration complete. Could not rename old directory — remove /workspaces/.claude/ manually" + fi +else + echo "[setup-migrate] ERROR: cp failed — check output above for details" + echo "[setup-migrate] Old directory preserved at $OLD_DIR" + exit 1 +fi diff --git a/.devcontainer/scripts/setup-symlink-claude.sh b/.devcontainer/scripts/setup-symlink-claude.sh deleted file mode 100755 index 29d2298..0000000 --- a/.devcontainer/scripts/setup-symlink-claude.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Symlink $HOME/.claude → $CLAUDE_CONFIG_DIR so third-party tools -# (ccburn, ccusage, etc.) that hardcode ~/.claude can find auth and config. - -CLAUDE_DIR="${CLAUDE_CONFIG_DIR:=/workspaces/.claude}" -HOME_CLAUDE="$HOME/.claude" - -echo "[setup-symlink] Ensuring $HOME_CLAUDE → $CLAUDE_DIR ..." - -# Already a correct symlink — nothing to do -if [ -L "$HOME_CLAUDE" ]; then - CURRENT_TARGET="$(readlink "$HOME_CLAUDE")" - if [ "$CURRENT_TARGET" = "$CLAUDE_DIR" ]; then - echo "[setup-symlink] Symlink already correct, skipping" - exit 0 - fi - # Points somewhere else — remove stale symlink - echo "[setup-symlink] Removing stale symlink ($CURRENT_TARGET)" - rm "$HOME_CLAUDE" -fi - -# Real directory exists — merge contents into target, then remove -if [ -d "$HOME_CLAUDE" ]; then - echo "[setup-symlink] Moving existing $HOME_CLAUDE contents into $CLAUDE_DIR" - mkdir -p "$CLAUDE_DIR" - # Copy contents preserving attributes; skip files that already exist in target - cp -rn "$HOME_CLAUDE/." "$CLAUDE_DIR/" 2>/dev/null || true - rm -rf "$HOME_CLAUDE" -fi - -# Ensure target exists -mkdir -p "$CLAUDE_DIR" - -# Create symlink -ln -s "$CLAUDE_DIR" "$HOME_CLAUDE" -echo "[setup-symlink] Created symlink: $HOME_CLAUDE → $CLAUDE_DIR" diff --git a/.devcontainer/scripts/setup.sh b/.devcontainer/scripts/setup.sh index 8c541e9..d865c67 100755 --- a/.devcontainer/scripts/setup.sh +++ b/.devcontainer/scripts/setup.sh @@ -12,8 +12,22 @@ if [ -f "$ENV_FILE" ]; then set +a fi +# Deprecation guard: .env may still set CLAUDE_CONFIG_DIR=/workspaces/.claude +# (pre-v2.0 default). Since .env is gitignored, PR updates can't fix it. +# Override with warning so all child scripts use the correct home location. +if [ "$CLAUDE_CONFIG_DIR" = "/workspaces/.claude" ]; then + echo "[setup] WARNING: CLAUDE_CONFIG_DIR=/workspaces/.claude is deprecated (moved to home dir in v2.0)" + echo "[setup] Updating .devcontainer/.env automatically." + CLAUDE_CONFIG_DIR="$HOME/.claude" + # Fix the file on disk so subsequent restarts don't trigger this guard + if [ -f "$ENV_FILE" ]; then + sed -i 's|^CLAUDE_CONFIG_DIR=.*/workspaces/\.claude.*|# CLAUDE_CONFIG_DIR removed (v2.0: now uses $HOME/.claude)|' "$ENV_FILE" + echo "[setup] .env updated — CLAUDE_CONFIG_DIR line commented out." + fi +fi + # Apply defaults for any unset variables -: "${CLAUDE_CONFIG_DIR:=/workspaces/.claude}" +: "${CLAUDE_CONFIG_DIR:=$HOME/.claude}" : "${CONFIG_SOURCE_DIR:=$DEVCONTAINER_DIR/config}" : "${SETUP_CONFIG:=true}" : "${SETUP_ALIASES:=true}" @@ -26,6 +40,12 @@ fi export CLAUDE_CONFIG_DIR CONFIG_SOURCE_DIR SETUP_CONFIG SETUP_ALIASES SETUP_AUTH SETUP_PLUGINS SETUP_UPDATE_CLAUDE SETUP_PROJECTS SETUP_TERMINAL SETUP_POSTSTART +# Fix named volume ownership — Docker creates named volumes as root:root +# regardless of remoteUser. This is the only setup script requiring sudo. +if ! sudo chown "$(id -un):$(id -gn)" "$HOME/.claude" 2>/dev/null; then + echo "[setup] WARNING: Could not fix volume ownership on $HOME/.claude — subsequent scripts may fail" +fi + SETUP_START=$(date +%s) SETUP_RESULTS=() @@ -88,7 +108,7 @@ run_poststart_hooks() { fi } -run_script "$SCRIPT_DIR/setup-symlink-claude.sh" "true" +run_script "$SCRIPT_DIR/setup-migrate-claude.sh" "true" run_script "$SCRIPT_DIR/setup-auth.sh" "$SETUP_AUTH" run_script "$SCRIPT_DIR/setup-config.sh" "$SETUP_CONFIG" run_script "$SCRIPT_DIR/setup-aliases.sh" "$SETUP_ALIASES" diff --git a/docs/src/content/docs/customization/configuration.md b/docs/src/content/docs/customization/configuration.md index ccf2616..aca08c7 100644 --- a/docs/src/content/docs/customization/configuration.md +++ b/docs/src/content/docs/customization/configuration.md @@ -9,7 +9,7 @@ CodeForge configuration is spread across three files that each control a differe ## settings.json -The primary configuration file lives at `.devcontainer/config/defaults/settings.json`. It is deployed to `.claude/settings.json` on every container start and controls Claude Code's runtime behavior. +The primary configuration file lives at `.devcontainer/config/defaults/settings.json`. It is deployed to `~/.claude/settings.json` on every container start and controls Claude Code's runtime behavior. ### Core Settings @@ -116,7 +116,7 @@ The `statusLine` block configures the terminal status bar: ## file-manifest.json -The file manifest at `.devcontainer/config/file-manifest.json` controls which configuration files are deployed to `.claude/` and how they are updated. Each entry specifies a source file, a destination, and an overwrite strategy: +The file manifest at `.devcontainer/config/file-manifest.json` controls which configuration files are deployed to `~/.claude/` and how they are updated. Each entry specifies a source file, a destination, and an overwrite strategy: ```json [ @@ -155,7 +155,7 @@ If you customize a deployed file (like `settings.json` or the system prompt) and ### Adding a New Config File -To deploy a new file to `.claude/` automatically: +To deploy a new file to `~/.claude/` automatically: 1. Place the file in `.devcontainer/config/defaults/` 2. Add an entry to `file-manifest.json` diff --git a/docs/src/content/docs/customization/rules.md b/docs/src/content/docs/customization/rules.md index e9114ea..f92ee6f 100644 --- a/docs/src/content/docs/customization/rules.md +++ b/docs/src/content/docs/customization/rules.md @@ -11,7 +11,7 @@ Rules are Markdown files that define hard constraints applied to every Claude Co Rule files are Markdown documents placed in `.claude/rules/`. Claude Code loads every `.md` file in this directory at session start and treats their contents as mandatory instructions. The filename is descriptive but does not affect loading -- all files are loaded equally. -Rules are deployed from `.devcontainer/config/defaults/rules/` to `.claude/rules/` via the file manifest on every container start. You can also add rules directly to `.claude/rules/` in your project. +Rules are deployed from `.devcontainer/config/defaults/rules/` to `~/.claude/rules/` via the file manifest on every container start. You can also add rules directly to `.claude/rules/` in your project. ### Rule Precedence diff --git a/docs/src/content/docs/customization/system-prompts.md b/docs/src/content/docs/customization/system-prompts.md index 9a238be..cf2fdfc 100644 --- a/docs/src/content/docs/customization/system-prompts.md +++ b/docs/src/content/docs/customization/system-prompts.md @@ -12,7 +12,7 @@ System prompts define how Claude Code behaves during your sessions -- its coding The main system prompt is loaded for every `cc` or `claude` session. It is the single most influential file in shaping how Claude works with your code. **Location:** `.devcontainer/config/defaults/main-system-prompt.md` -**Deployed to:** `.claude/main-system-prompt.md` +**Deployed to:** `~/.claude/main-system-prompt.md` ### What It Controls @@ -53,7 +53,7 @@ Each section is self-contained. You can edit, remove, or add sections independen The writing system prompt is activated when you launch Claude with the `ccw` command. It replaces the development-focused prompt with one tuned for creative fiction writing. **Location:** `.devcontainer/config/defaults/writing-system-prompt.md` -**Deployed to:** `.claude/writing-system-prompt.md` +**Deployed to:** `~/.claude/writing-system-prompt.md` ### Key Differences from Main Prompt @@ -70,9 +70,9 @@ Use `cc` for coding sessions and `ccw` for writing sessions. Both are shell alia ### Editing the Main Prompt -To change development behavior, edit `.devcontainer/config/defaults/main-system-prompt.md`. Your changes are deployed to `.claude/` on the next container start via the file manifest. +To change development behavior, edit `.devcontainer/config/defaults/main-system-prompt.md`. Your changes are deployed to `~/.claude/` on the next container start via the file manifest. -For changes to take effect immediately (without restarting the container), edit the deployed copy at `.claude/main-system-prompt.md` directly. Be aware that this copy will be overwritten on the next container rebuild unless you change the overwrite mode in `file-manifest.json`. +For changes to take effect immediately (without restarting the container), edit the deployed copy at `~/.claude/main-system-prompt.md` directly. Be aware that this copy will be overwritten on the next container rebuild unless you change the overwrite mode in `file-manifest.json`. ### What to Customize @@ -165,7 +165,7 @@ Rules override the system prompt when they conflict. CLAUDE.md provides context ## Deployment and File Manifest -Both system prompts are listed in `file-manifest.json` and deployed to `.claude/` on every container start: +Both system prompts are listed in `file-manifest.json` and deployed to `~/.claude/` on every container start: ```json { @@ -179,7 +179,7 @@ Both system prompts are listed in `file-manifest.json` and deployed to `.claude/ The `if-changed` mode means your deployed copy is only overwritten when the source file's SHA-256 hash changes. If you want to make persistent local edits to the deployed prompt, change the overwrite mode to `"never"` so your changes survive container rebuilds. :::note[Two Copies] -The source file at `.devcontainer/config/defaults/main-system-prompt.md` is the canonical version. The deployed copy at `.claude/main-system-prompt.md` is what Claude Code actually loads. Edits to the source are deployed on next container start. Edits to the deployed copy take effect immediately but may be overwritten. +The source file at `.devcontainer/config/defaults/main-system-prompt.md` is the canonical version. The deployed copy at `~/.claude/main-system-prompt.md` is what Claude Code actually loads. Edits to the source are deployed on next container start. Edits to the deployed copy take effect immediately but may be overwritten. ::: ## Related diff --git a/docs/src/content/docs/plugins/workspace-scope-guard.md b/docs/src/content/docs/plugins/workspace-scope-guard.md index 5e8fe60..bdab9dc 100644 --- a/docs/src/content/docs/plugins/workspace-scope-guard.md +++ b/docs/src/content/docs/plugins/workspace-scope-guard.md @@ -74,7 +74,7 @@ A minimal set of paths are always allowed: | Allowed Path | Reason | |-------------|--------| -| `/workspaces/.claude/` | Claude config, plans, rules | +| `~/.claude/` | Claude config, plans, rules | | `/tmp/` | System temp directory | ## Bash Enforcement diff --git a/docs/src/content/docs/reference/architecture.md b/docs/src/content/docs/reference/architecture.md index 3d52cb7..06fea16 100644 --- a/docs/src/content/docs/reference/architecture.md +++ b/docs/src/content/docs/reference/architecture.md @@ -199,7 +199,7 @@ devcontainer.json | v [postStartCommand] setup.sh orchestrates: - 1. setup-symlink-claude.sh -- Symlink Claude config directory + 1. setup-migrate-claude.sh -- Migrate Claude config from old location 2. setup-auth.sh -- Git/NPM authentication 3. setup-config.sh -- Deploy settings, prompts, rules via file-manifest.json 4. setup-aliases.sh -- Write shell aliases to .bashrc/.zshrc diff --git a/docs/src/content/docs/reference/changelog.md b/docs/src/content/docs/reference/changelog.md index 4de958a..6ef6048 100644 --- a/docs/src/content/docs/reference/changelog.md +++ b/docs/src/content/docs/reference/changelog.md @@ -49,11 +49,44 @@ For minor and patch updates, you can usually just rebuild the container. Check t ## Unreleased +### Changed + +#### Configuration +- Moved `.claude` directory from `/workspaces/.claude` to `~/.claude` (home directory) +- Added Docker named volume for persistence across rebuilds (per-instance isolation via `${devcontainerId}`) +- `CLAUDE_CONFIG_DIR` now defaults to `~/.claude` + +#### Authentication +- Added `CLAUDE_AUTH_TOKEN` support in `.secrets` for long-lived tokens from `claude setup-token` +- Auto-creates `.credentials.json` from token on container start (skips if already exists) +- Added `CLAUDE_AUTH_TOKEN` to devcontainer.json secrets declaration + +#### Security +- Protected-files-guard now blocks modifications to `.credentials.json` +- Replaced `eval` tilde expansion with `getent passwd` lookup across all scripts (prevents shell injection via `SUDO_USER`/`USER`) +- Auth token value is now JSON-escaped before writing to `.credentials.json` +- Credential directory created with restrictive umask (700) matching credential file permissions (600) + +#### Scripts +- Replaced `setup-symlink-claude.sh` with `setup-migrate-claude.sh` (one-time migration) +- Auto-migrates from `/workspaces/.claude/` if `.credentials.json` present +- `chown` in mcp-qdrant poststart hooks now uses resolved `_USERNAME` instead of hardcoded `vscode` or `$(id -un)` + +#### Documentation +- All docs now reference `~/.claude` as default config path +- Added `CLAUDE_AUTH_TOKEN` setup flow to README, configuration reference, and troubleshooting +- ccstatusline README verification commands now respect `CLAUDE_CONFIG_DIR` + ### Removed +#### Scripts +- `setup-symlink-claude.sh` — no longer needed with native home directory location + #### VS Code Extensions - **Todo+** (`fabiospampinato.vscode-todo-plus`) — removed from devcontainer extensions +--- + ## v1.14.2 **Release date:** 2026-02-24 diff --git a/docs/src/content/docs/reference/environment.md b/docs/src/content/docs/reference/environment.md index f6afe75..77525da 100644 --- a/docs/src/content/docs/reference/environment.md +++ b/docs/src/content/docs/reference/environment.md @@ -17,7 +17,7 @@ Variables that control Claude Code's core behavior inside the CodeForge containe | `ANTHROPIC_DEFAULT_OPUS_MODEL` | Opus model ID | `claude-opus-4-6` | settings.json | | `ANTHROPIC_DEFAULT_SONNET_MODEL` | Sonnet model ID | `claude-sonnet-4-5-20250929` | settings.json | | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | Haiku model ID | `claude-haiku-4-5-20251001` | settings.json | -| `CLAUDE_CONFIG_DIR` | Claude Code configuration directory | `/workspaces/.claude` | devcontainer.json | +| `CLAUDE_CONFIG_DIR` | Claude Code configuration directory | `/home/vscode/.claude` | devcontainer.json | | `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | Maximum tokens per response | `64000` | settings.json | | `MAX_THINKING_TOKENS` | Maximum tokens for extended thinking | `63999` | settings.json | | `CLAUDE_CODE_SHELL` | Shell used for Bash tool execution | `zsh` | settings.json | @@ -71,7 +71,7 @@ Variables set by the DevContainer environment that define workspace paths. | Variable | Description | Default | Set In | |----------|-------------|---------|--------| | `WORKSPACE_ROOT` | Workspace root directory | `/workspaces` | devcontainer.json | -| `CLAUDE_CONFIG_DIR` | Claude configuration directory | `/workspaces/.claude` | devcontainer.json | +| `CLAUDE_CONFIG_DIR` | Claude configuration directory | `/home/vscode/.claude` | devcontainer.json | | `GH_CONFIG_DIR` | GitHub CLI configuration directory | `/workspaces/.gh` | devcontainer.json | | `TMPDIR` | Temporary files directory | `/workspaces/.tmp` | devcontainer.json | | `CLAUDECODE` | Set to `null` to unset the detection flag, enabling nested Claude Code sessions | `null` | devcontainer.json | @@ -112,7 +112,7 @@ Applied when the container is created. Persists across all sessions. { "remoteEnv": { "WORKSPACE_ROOT": "/workspaces", - "CLAUDE_CONFIG_DIR": "/workspaces/.claude" + "CLAUDE_CONFIG_DIR": "/home/vscode/.claude" } } ```