Skip to content

Commit 4c0b43a

Browse files
authored
feat(config): move .claude to home directory with named volume and auth token support (#3)
* Move .claude config from /workspaces to home directory with named volume - Change CLAUDE_CONFIG_DIR from /workspaces/.claude to ~/.claude - Add Docker named volume (per-instance via ${devcontainerId}) for persistence - Add CLAUDE_AUTH_TOKEN secret support with auto .credentials.json creation - Replace setup-symlink-claude.sh with setup-migrate-claude.sh (one-time migration) - Fix named volume root ownership via sudo chown on startup - Update scope guard allowlist to resolve $HOME dynamically - Update protected-files guard regex to cover .credentials.json - Update all feature heredocs, poststart hooks, and docs * Harden auth token handling, migration, and volume ownership Security and robustness fixes from PR review: - setup-auth.sh: Replace unquoted heredoc with printf '%s' to prevent shell injection via CLAUDE_AUTH_TOKEN metacharacters (H1) - setup-auth.sh: Add sk-ant-* format validation on token (L1) - setup-auth.sh: Check/fix 600 permissions on existing .credentials.json (L2) - setup-auth.sh: Use ${CLAUDE_CONFIG_DIR:-$HOME/.claude} pattern consistently with other scripts (M3) - setup-auth.sh: Document /proc token visibility limitation (M2) - setup-migrate-claude.sh: Add idempotency check — skip silently when destination already has content (H2) - setup-migrate-claude.sh: Broaden trigger — migrate all content, not just when .credentials.json exists (L3) - setup-migrate-claude.sh: Add symlink protection on old directory (M1) - setup-migrate-claude.sh: Use --no-dereference with cp (M1) - setup-migrate-claude.sh: Use ${CLAUDE_CONFIG_DIR} pattern (L4) - setup.sh: Log warning on sudo chown failure instead of silent suppression (M4) * Address CodeRabbit review findings (1, 2, 6, 7) - CHANGELOG.md: Add #### Documentation subsection under Changed, add #### Scripts subsection under Removed for consistent structure - CLAUDE.md: Document CLAUDE_AUTH_TOKEN, .credentials.json auto-creation, skip-if-exists behavior, sk-ant-* validation, and named volume persistence - setup-auth.sh: Detect printf subshell write failure — report warning instead of false success when .credentials.json write fails - setup-migrate-claude.sh: Verify cp exit status before printing success — warn if copy failed instead of unconditional "Migration complete" - docs/reference/changelog.md: Mirror CHANGELOG structure fixes Findings 3-5 (feature $HOME fallback) confirmed as false positives: postStartCommand runs as vscode user, CLAUDE_CONFIG_DIR is exported by setup.sh before hooks execute. * Address remaining CodeRabbit review findings (3, 4, 5, 8, 9, 10) - Fix hardcoded /home/vscode/.claude in changelog, use portable ~/.claude - Remove implementation detail "(leading dot)" from changelog entry - Set AUTH_CONFIGURED=true when credentials already exist (fixes false "No tokens provided" summary) - Update docs site settings.json deployment path to ~/.claude - Harden $HOME fallback across all scripts: resolve target user's home via SUDO_USER/USER/vscode chain instead of relying on $HOME (guards against root context in feature installs and hooks) - Add CLAUDE_CONFIG_DIR documentation to ccstatusline and mcp-qdrant feature READMEs - Fix stale .claude/settings.json references in ccstatusline README * Harden shell scripts and fix stale docs from CodeRabbit review - Replace eval tilde expansion with getent passwd lookup in all PR-scoped scripts (setup-auth, setup-migrate, ccstatusline, mcp-qdrant install + poststart hook) to prevent shell injection via SUDO_USER/USER environment variables - JSON-escape auth token value before writing .credentials.json - Create credential directory with umask 077 (was default 755) - Fix mcp-qdrant chown to use resolved _USERNAME instead of hardcoded vscode or $(id -un) - Update ccstatusline README verification commands to respect CLAUDE_CONFIG_DIR environment variable - Sync docs site changelog with devcontainer CHANGELOG fixes (~/. claude path, remove "(leading dot)" aside) * fix(migration): harden migration script and add .env deprecation guard Migration script: - Switch from cp -rn to cp -a (archive mode) for faithful copy - Marker-based idempotency instead of checking destination contents - Verify critical files (.claude.json, plugins/, .credentials.json) - Fix ownership after copy (source may have different uid) - Rename old directory to .bak on success Setup.sh: - Detect stale CLAUDE_CONFIG_DIR=/workspaces/.claude in .env - Override to $HOME/.claude with warning - Auto-comment the stale line on disk --------- Co-authored-by: AnExiledDev <AnExiledDev@users.noreply.github.com>
1 parent 873bb71 commit 4c0b43a

File tree

32 files changed

+312
-88
lines changed

32 files changed

+312
-88
lines changed

.devcontainer/.env.example

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# CodeForge Environment Configuration
22
# Copy to .env and customize. .env is gitignored.
33

4-
# Paths
5-
CLAUDE_CONFIG_DIR=/workspaces/.claude
6-
# CONFIG_SOURCE_DIR is derived from script location; uncomment to override:
4+
# Paths (defaults shown — uncomment to override)
5+
# CLAUDE_CONFIG_DIR=$HOME/.claude
76
# CONFIG_SOURCE_DIR=/custom/path/to/config
87

98
# Setup: copy config files to CLAUDE_CONFIG_DIR (per config/file-manifest.json)

.devcontainer/.secrets.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ GH_EMAIL=
1010

1111
# NPM auth token for registry.npmjs.org
1212
NPM_TOKEN=
13+
14+
# Claude long-lived auth token (from 'claude setup-token')
15+
CLAUDE_AUTH_TOKEN=

.devcontainer/CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,37 @@
99

1010
### Changed
1111

12+
#### Configuration
13+
- Moved `.claude` directory from `/workspaces/.claude` to `~/.claude` (home directory)
14+
- Added Docker named volume for persistence across rebuilds (per-instance isolation via `${devcontainerId}`)
15+
- `CLAUDE_CONFIG_DIR` now defaults to `~/.claude`
16+
17+
#### Authentication
18+
- Added `CLAUDE_AUTH_TOKEN` support in `.secrets` for long-lived tokens from `claude setup-token`
19+
- Auto-creates `.credentials.json` from token on container start (skips if already exists)
20+
- Added `CLAUDE_AUTH_TOKEN` to devcontainer.json secrets declaration
21+
22+
#### Security
23+
- Protected-files-guard now blocks modifications to `.credentials.json`
24+
- Replaced `eval` tilde expansion with `getent passwd` lookup across all scripts (prevents shell injection via `SUDO_USER`/`USER`)
25+
- Auth token value is now JSON-escaped before writing to `.credentials.json`
26+
- Credential directory created with restrictive umask (700) matching credential file permissions (600)
27+
1228
#### Status Bar
1329
- **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
1430

31+
#### Scripts
32+
- Replaced `setup-symlink-claude.sh` with `setup-migrate-claude.sh` (one-time migration)
33+
- Auto-migrates from `/workspaces/.claude/` if `.credentials.json` present
34+
- `chown` in mcp-qdrant poststart hooks now uses resolved `_USERNAME` instead of hardcoded `vscode` or `$(id -un)`
35+
- **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
36+
- **`.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
37+
38+
#### Documentation
39+
- All docs now reference `~/.claude` as default config path
40+
- Added `CLAUDE_AUTH_TOKEN` setup flow to README, configuration reference, and troubleshooting
41+
- ccstatusline README verification commands now respect `CLAUDE_CONFIG_DIR`
42+
1543
### Fixed
1644

1745
#### Plugin Marketplace
@@ -29,6 +57,9 @@
2957

3058
### Removed
3159

60+
#### Scripts
61+
- `setup-symlink-claude.sh` — no longer needed with native home directory location
62+
3263
#### VS Code Extensions
3364
- **Todo+** (`fabiospampinato.vscode-todo-plus`) — removed from devcontainer extensions
3465

.devcontainer/CLAUDE.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ CodeForge devcontainer for AI-assisted development with Claude Code.
3131
| `devcontainer.json` | Container definition: image, features, mounts |
3232
| `.env` | Boolean flags controlling setup steps |
3333

34-
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}`.
34+
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}`.
3535

3636
## Commands
3737

@@ -76,14 +76,21 @@ Rules in `config/defaults/rules/` deploy to `.claude/rules/` on every container
7676

7777
| Variable | Value |
7878
|----------|-------|
79-
| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` |
79+
| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` |
80+
| `CLAUDE_AUTH_TOKEN` | Long-lived token from `claude setup-token` (optional, via `.secrets` or Codespaces secrets) |
8081
| `ANTHROPIC_MODEL` | `claude-opus-4-6` |
8182
| `WORKSPACE_ROOT` | `/workspaces` |
8283
| `TERM` | `${localEnv:TERM:xterm-256color}` (via `remoteEnv` — forwards host TERM, falls back to 256-color) |
8384
| `COLORTERM` | `truecolor` (via `remoteEnv` — enables 24-bit color support) |
8485

8586
All experimental feature flags are in `settings.json` under `env`. Setup steps controlled by boolean flags in `.env`.
8687

88+
## Authentication & Persistence
89+
90+
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.
91+
92+
**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.
93+
8794
## Modifying Behavior
8895

8996
1. **Change model**: Edit `config/defaults/settings.json``"model"` field

.devcontainer/README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,20 @@ Get an API key from [console.anthropic.com](https://console.anthropic.com/).
4040

4141
### Credential Persistence
4242

43-
Authentication credentials are stored in `/workspaces/.claude/` and persist across container rebuilds.
43+
Authentication credentials are stored in `~/.claude/` and persist across container rebuilds via a Docker named volume.
44+
45+
### Long-Lived Token Authentication
46+
47+
For headless or automated environments, you can use a long-lived auth token instead of browser login:
48+
49+
1. Generate a token: `claude setup-token`
50+
2. Add to `.devcontainer/.secrets`:
51+
```bash
52+
CLAUDE_AUTH_TOKEN=sk-ant-oat01-your-token-here
53+
```
54+
3. On next container start, `setup-auth.sh` will create `~/.claude/.credentials.json` automatically.
55+
56+
You can also set `CLAUDE_AUTH_TOKEN` as a Codespaces secret for cloud environments.
4457

4558
For more options, see the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code).
4659

@@ -111,7 +124,7 @@ Expected output shows your authenticated account and token scopes.
111124

112125
### Credential Persistence
113126

114-
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.
127+
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/`.
115128

116129
**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.
117130

@@ -199,7 +212,7 @@ Copy `.devcontainer/.env.example` to `.devcontainer/.env` and customize:
199212

200213
| Variable | Default | Description |
201214
|----------|---------|-------------|
202-
| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Claude configuration directory |
215+
| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Claude configuration directory |
203216
| `SETUP_CONFIG` | `true` | Copy config files during setup (per `file-manifest.json`) |
204217
| `SETUP_ALIASES` | `true` | Add `cc`/`claude`/`ccraw` aliases to shell |
205218
| `SETUP_AUTH` | `true` | Configure Git/NPM auth from `.secrets` |
@@ -301,6 +314,8 @@ Three methods for providing GitHub/NPM credentials, in order of precedence:
301314

302315
All methods persist across container rebuilds via the bind-mounted `/workspaces/.gh/` directory.
303316

317+
4. **`.secrets` file with `CLAUDE_AUTH_TOKEN`** — Long-lived Claude auth token from `claude setup-token`. Auto-creates `~/.claude/.credentials.json` on container start.
318+
304319
## Agents & Skills
305320

306321
Agents and skills are distributed across focused plugins (replacing the former `code-directive` monolith).

.devcontainer/devcontainer.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
"workspaceFolder": "/workspaces",
66
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces,type=bind",
77

8+
"mounts": [
9+
{
10+
"source": "codeforge-claude-config-${devcontainerId}",
11+
"target": "/home/vscode/.claude",
12+
"type": "volume"
13+
}
14+
],
15+
816
"remoteEnv": {
917
"WORKSPACE_ROOT": "/workspaces",
10-
"CLAUDE_CONFIG_DIR": "/workspaces/.claude",
18+
"CLAUDE_CONFIG_DIR": "/home/vscode/.claude",
1119
"GH_CONFIG_DIR": "/workspaces/.gh",
1220
"TMPDIR": "/workspaces/.tmp",
1321
"TERM": "${localEnv:TERM:xterm-256color}",
@@ -29,6 +37,10 @@
2937
},
3038
"GH_EMAIL": {
3139
"description": "GitHub email for git config (optional)"
40+
},
41+
"CLAUDE_AUTH_TOKEN": {
42+
"description": "Claude long-lived auth token from 'claude setup-token' (optional - sk-ant-oat01-*)",
43+
"documentationUrl": "https://docs.anthropic.com/en/docs/claude-code/cli-reference#claude-setup-token"
3244
}
3345
},
3446

.devcontainer/docs/configuration-reference.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ These control what `setup.sh` does on each container start. Copy `.env.example`
2323

2424
| Variable | Default | Description |
2525
|----------|---------|-------------|
26-
| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Where Claude Code config files are stored |
26+
| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Where Claude Code config files are stored |
2727
| `CONFIG_SOURCE_DIR` | `(auto-detected)` | Source directory for config defaults |
2828
| `SETUP_CONFIG` | `true` | Copy config files per `file-manifest.json` |
2929
| `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
4242
| Variable | Value | Description |
4343
|----------|-------|-------------|
4444
| `WORKSPACE_ROOT` | `/workspaces` | Workspace root directory |
45-
| `CLAUDE_CONFIG_DIR` | `/workspaces/.claude` | Claude Code config directory |
45+
| `CLAUDE_CONFIG_DIR` | `/home/vscode/.claude` | Claude Code config directory |
4646
| `GH_CONFIG_DIR` | `/workspaces/.gh` | GitHub CLI config directory |
4747
| `TMPDIR` | `/workspaces/.tmp` | Temporary files directory |
4848
| `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
8888
GH_USERNAME=your-github-username
8989
GH_EMAIL=your-email@example.com
9090
NPM_TOKEN=npm_your_token_here
91+
CLAUDE_AUTH_TOKEN=sk-ant-oat01-your-token-here
9192
```
9293

94+
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).
95+
9396
Environment variables with the same names take precedence over `.secrets` file values (useful for Codespaces).

.devcontainer/docs/keybindings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Edit `config/defaults/keybindings.json` to remap Claude Code actions to non-conf
7878
}
7979
```
8080

81-
The keybindings file is copied to `/workspaces/.claude/keybindings.json` on container start (controlled by `file-manifest.json`).
81+
The keybindings file is copied to `~/.claude/keybindings.json` on container start (controlled by `file-manifest.json`).
8282

8383
## Claude Code Keybinding Reference
8484

.devcontainer/docs/troubleshooting.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ Common issues and solutions for the CodeForge devcontainer.
3232
- Or configure `.devcontainer/.secrets` with `GH_TOKEN` for automatic auth on container start.
3333
- Credentials persist in `/workspaces/.gh/` across rebuilds.
3434

35+
**Problem**: Claude auth token not taking effect in Codespaces.
36+
37+
- 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.
38+
- If `.credentials.json` already exists, the token injection is skipped (idempotent). Delete `~/.claude/.credentials.json` to force re-creation from the token.
39+
3540
**Problem**: Git push fails with permission error.
3641

3742
- Run `gh auth status` to verify authentication.
@@ -119,7 +124,7 @@ Common issues and solutions for the CodeForge devcontainer.
119124

120125
## How to Reset to Defaults
121126

122-
1. **Reset config files**: Delete `/workspaces/.claude/` and restart the container. `setup-config.sh` will recopy all files from `config/defaults/`.
127+
1. **Reset config files**: Delete `~/.claude/` and restart the container. `setup-config.sh` will recopy all files from `config/defaults/`.
123128

124129
2. **Reset aliases**: Delete the `# Claude Code environment and aliases` block from `~/.bashrc` and `~/.zshrc`, then run `bash /workspaces/.devcontainer/scripts/setup-aliases.sh`.
125130

.devcontainer/features/ccstatusline/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ All widgets connected with powerline arrows (monokai theme).
4545

4646
- **ccstatusline npm package**: Installed on-demand via `npx` (not globally)
4747
- **Configuration file**: `~/.config/ccstatusline/settings.json` with powerline theme
48-
- **Claude Code integration**: Automatically updates `.claude/settings.json`
48+
- **Claude Code integration**: Automatically updates `~/.claude/settings.json`
4949
- **Disk Usage**: Minimal (~2MB when cached by npx)
5050

5151
## Requirements
@@ -75,17 +75,18 @@ The feature will validate these are present and exit with an error if missing.
7575
-**Session Resume**: Copyable `cc --resume {sessionId}` command via custom-command widget
7676
-**Burn Rate Tracking**: Live ccburn compact output showing pace indicators (🧊/🔥/🚨)
7777
-**ANSI Colors**: High-contrast colors optimized for dark terminals
78-
-**Automatic Integration**: Auto-configures `.claude/settings.json`
78+
-**Automatic Integration**: Auto-configures `~/.claude/settings.json`
7979
-**Idempotent**: Safe to run multiple times
8080
-**Multi-user**: Automatically detects container user
81+
-**Config-aware**: Respects `CLAUDE_CONFIG_DIR` environment variable (defaults to `~/.claude`)
8182

8283
## Post-Installation Steps
8384

8485
### ✅ Configuration is Automatic
8586

8687
This feature automatically:
8788
1. Creates `~/.config/ccstatusline/settings.json` with powerline configuration
88-
2. Configures `.claude/settings.json` to use ccstatusline
89+
2. Configures `~/.claude/settings.json` to use ccstatusline
8990

9091
**No manual steps required!**
9192

@@ -105,7 +106,7 @@ You should see formatted output with powerline styling.
105106

106107
**3. Check Claude Code integration:**
107108
```bash
108-
cat /workspaces/.claude/settings.json | jq '.statusLine'
109+
cat "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" | jq '.statusLine'
109110
```
110111

111112
Should show:
@@ -204,7 +205,7 @@ cat ~/.config/ccstatusline/settings.json | jq .
204205
echo '{"model":{"display_name":"Test"}}' | npx -y ccstatusline@latest
205206

206207
# 3. Check Claude Code settings
207-
cat /workspaces/.claude/settings.json | jq '.statusLine'
208+
cat "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" | jq '.statusLine'
208209

209210
# 4. Manually run auto-config if needed
210211
configure-ccstatusline-auto
@@ -258,7 +259,7 @@ configure-ccstatusline-auto
258259
npm install -g ccstatusline@latest
259260
```
260261

261-
Then update `.claude/settings.json`:
262+
Then update `${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json`:
262263
```json
263264
{
264265
"statusLine": {

0 commit comments

Comments
 (0)