You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: describe the real ~/.claude mount instead of read-only protection that does not exist
The claude-code feature declares one mount, and it is a read-write bind of
the whole directory:
source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind
No `ro` flag appears anywhere in the feature, and there are no per-file
mounts. The README and TROUBLESHOOTING guide described something else: five
per-file read-only binds over CLAUDE.md, settings.json, agents/, commands/
and hooks/, said to prevent prompt injection and hook manipulation, with
only .credentials.json and .claude.json writable. None of that is
implemented, so both files now describe what the mount actually is -- one
shared read-write config directory -- and state the consequence: hooks/ and
settings.json are executed by Claude Code wherever it runs, so content a
container writes there runs on the host the next time Claude Code starts
there.
The worst of it was a documented debugging step:
echo "test" >> ~/.claude/CLAUDE.md # Should fail with "Read-only file system"
It does not fail. It appends to the developer's real global instructions
file, which is then loaded into every later Claude Code session. It is
replaced with a check that CLAUDE_CONFIG_DIR points at the mount and that
the directory is writable, using a throwaway file it deletes.
Documentation only -- no mount, feature or script is changed. Whether the
non-credential paths should actually become read-only binds is left open.
- Prompt injection attacks that could modify your Claude configuration
31
-
- Accidental modification of shared configuration from within containers
32
-
- Security issues related to hook manipulation
26
+
There is no `ro` flag and no per-file mount. The host and every container of every branch share one `~/.claude`, read-write, so anything running in a container can modify any of it -- `CLAUDE.md`, `settings.json`, `agents/`, `commands/` and `hooks/` included.
-`~/.claude/.claude.json` → Account info, user ID, workspace setup tracking
28
+
### What That Means
37
29
38
-
These files **must be writable** to enable:
39
-
- OAuth authentication flow and token refresh
40
-
- Workspace setup state tracking (`projectOnboardingSeenCount`)
41
-
- Session continuity across container rebuilds
30
+
`hooks/` and `settings.json` are executed by Claude Code wherever it runs. Content a container writes there therefore runs on the **host**, the next time Claude Code starts on the host, and a `postCreateCommand` from a repository you have not read is enough to put it there.
42
31
43
-
### Why These Must Be Writable
32
+
It is not a confidentiality boundary either: code running in the container holds the live Claude credentials the mount carries, and under `dl` a `GH_TOKEN` with repo and workflow scopes.
44
33
45
-
**`.credentials.json`**: OAuth tokens need to be refreshed periodically. Claude writes updated tokens to this file.
34
+
### Why It Is Still One Read-Write Directory
46
35
47
-
**`.claude.json`**: Claude tracks per-workspace setup state here. The `projectOnboardingSeenCount` field must be writable so Claude doesn't show the setup wizard on every launch.
36
+
Sharing the directory is what makes credentials work across the host and every branch container. `.credentials.json` has to be writable because the access token is short-lived and a refresh has to persist -- a read-only or copied arrangement drifts into a re-auth. `.claude.json` has to be writable because Claude tracks per-workspace onboarding and trust state there, so a container that cannot write it re-onboards on every launch.
48
37
49
-
⚠️ **Security Note**: These files contain sensitive data and are mounted read-write by necessity. They are only accessible by the container user and stored with `600` permissions. Only use this feature with trusted repositories.
38
+
Splitting the rest of the directory into separate read-only binds is possible and is not what this feature does today. What the container buys as it stands is reproducible dependencies and non-colliding concurrent work, not safety against hostile code. `blooop/wayfinder`'s `.devcontainer/devcontainer.json` states the same threat model in the comment above its `mounts` block.
50
39
51
40
## Usage
52
41
@@ -193,11 +182,15 @@ Check mounted files:
193
182
ls -la ~/.claude/
194
183
```
195
184
196
-
Verify mounts are read-only:
185
+
Verify the config directory is mounted, writable, and pointed at:
197
186
```bash
198
-
echo"test">>~/.claude/CLAUDE.md # Should fail with "Read-only file system"
The write test uses a throwaway file on purpose. Do not test the mount by appending to `CLAUDE.md`, `settings.json` or anything under `hooks/`: the mount is read-write, so the write lands on the host's real configuration and is loaded into every later Claude Code session.
193
+
201
194
## Authentication
202
195
203
196
### How It Works
@@ -253,14 +246,9 @@ devpod up . --recreate
253
246
254
247
## Modifying Configuration
255
248
256
-
Configuration files (except credentials) are read-only. You **cannot** modify Claude settings from within the container.
249
+
The container writes to the same `~/.claude` as the host, so an edit made in either place is an edit to the one shared configuration.
257
250
258
-
To change configuration:
259
-
260
-
1. Edit files on your **host machine**: `~/.claude/settings.json`, `~/.claude/CLAUDE.md`, etc.
261
-
2. Restart or rebuild the container to see changes
262
-
263
-
This is by design for security (prevents prompt injection attacks).
251
+
Editing from the **host** is still the better habit: `~/.claude/settings.json`, `~/.claude/CLAUDE.md` and the rest are yours across every branch container, and a change made on the host is one you meant to make. The mount is live, so restarting `claude` picks up a change -- no rebuild needed.
`install.sh`creates the two credential files with `600` permissions when they do not already exist, which keeps other users on the host out. It does not restrict code running inside the container, which runs as the user those files belong to.
429
419
430
-
### What's Writable (Necessary Trade-off)
431
-
- **`.credentials.json`**: OAuth tokens must be writable for token refresh to work
432
-
- **`.claude.json`**: Workspace state must be writable to persist `projectOnboardingSeenCount` and other setup tracking
420
+
### The Consequence Worth Naming
421
+
`hooks/`and `settings.json` are executed by Claude Code wherever it runs. Code in the container that writes there gets its content executed on the **host**, the next time Claude Code starts there -- a `postCreateCommand` from a repository you have not read reaches that far. The container also carries the live Claude credentials and, under `dl`, a `GH_TOKEN` with repo and workflow scopes, so it is not a confidentiality boundary either.
433
422
434
-
### Security Mitigations
435
-
- Files have `600` permissions (user-only access)
436
-
- Only use this feature in **trusted repositories**
437
-
- Container user isolation provides some protection
438
-
- Writable files are limited to authentication/state only
439
-
- All configuration and code execution files remain read-only
423
+
### Why It Is Accepted
424
+
One shared config directory is what makes auth work across the host and every branch container without a re-auth, and it is why a container never re-onboards. That is the trade; the isolation buys reproducible dependencies and non-colliding concurrent work, not protection from hostile code. `blooop/wayfinder`'s `.devcontainer/devcontainer.json` writes out the same threat model above its `mounts` block. Treat a repository you launch this way as code you are running with your own credentials, because that is what it is.
440
425
441
-
### Known Risks
442
-
- A malicious process in the container could exfiltrate OAuth tokens from `.credentials.json`
443
-
- A malicious process could modify workspace state in `.claude.json`
444
-
- **Recommendation**: Only use in repositories you trust, as you would with any dev container configuration
426
+
Whether the non-credential paths should become read-only binds is an open question, not a settled one.
Copy file name to clipboardExpand all lines: .devcontainer/claude-code/TROUBLESHOOTING.md
+31-40Lines changed: 31 additions & 40 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,16 +5,18 @@
5
5
### Files That Must Exist on Host
6
6
7
7
```bash
8
-
~/.claude/
9
-
├── .credentials.json # OAuth tokens (must be writable)
10
-
├── .claude.json # Account info, setup state (must be writable)
11
-
├── CLAUDE.md # Global instructions (read-only)
12
-
├── settings.json # Settings (read-only)
13
-
├── agents/ # Custom agents (read-only)
14
-
├── commands/ # Custom commands (read-only)
15
-
└── hooks/ # Event hooks (read-only)
8
+
~/.claude/# one bind mount, read-write, shared with every container
9
+
├── .credentials.json # OAuth tokens
10
+
├── .claude.json # Account info, setup state
11
+
├── CLAUDE.md # Global instructions
12
+
├── settings.json # Settings
13
+
├── agents/ # Custom agents
14
+
├── commands/ # Custom commands
15
+
└── hooks/ # Event hooks
16
16
```
17
17
18
+
Every one of these is writable from inside the container, and a write lands on the host. See "Security Considerations" below for what follows from that.
19
+
18
20
### Critical Configuration in devcontainer.json
19
21
20
22
```json
@@ -129,47 +131,40 @@ Two different issues:
129
131
- Have to authenticate again
130
132
131
133
**Root Cause:**
132
-
`.credentials.json` or `.claude.json` is not mounted, or is mounted read-only.
134
+
`~/.claude` is not mounted, so `claude` wrote its credentials into the container's own filesystem and they went away with the container.
133
135
134
136
**Solution:**
135
137
136
-
1.**Verify mounts in container:**
138
+
1.**Verify the mount in the container:**
137
139
```bash
138
140
devpod ssh pythontemplate
139
141
mount | grep claude
140
142
```
141
143
142
-
Should show:
144
+
Should show one bind of the directory, read-write:
143
145
```
144
-
/dev/... on /home/vscode/.claude/.credentials.json type ext4 (rw,...)
145
-
/dev/... on /home/vscode/.claude/.claude.json type ext4 (rw,...)
146
+
/dev/... on /home/vscode/.claude type ext4 (rw,...)
146
147
```
147
148
148
149
2.**Check files exist on host:**
149
150
```bash
150
151
ls -la ~/.claude/.credentials.json ~/.claude/.claude.json
151
152
```
152
153
153
-
3.**Verify files are writable (not ro):**
154
-
The mounts MUST be read-write for auth to persist.
154
+
3.**Check the mount is read-write:**
155
+
A refresh has to persist, so `rw` in the line above is load-bearing. The feature declares no `ro` flag, so a read-only mount means something outside it added one.
155
156
156
-
### Issue 5: "Read-only file system" Error
157
+
### Issue 5: A Container Changed the Host's Claude Configuration
157
158
158
159
**Symptoms:**
159
-
- Error when trying to write to `~/.claude/CLAUDE.md` or similar
160
-
- Operations fail with "Read-only file system"
161
-
162
-
**Expected Behavior:**
163
-
This is intentional! Security files are mounted read-only:
-`~/.claude/CLAUDE.md`, `settings.json` or a file under `hooks/` differs from what you left on the host
161
+
- A hook or setting you did not write takes effect when you start `claude` on the host
165
162
166
-
**Why?**
167
-
Prevents prompt injection attacks that could modify your Claude configuration.
163
+
**Root Cause:**
164
+
Not a malfunction. `~/.claude` is one read-write bind of the whole directory, so the host and every container share it and anything in a container can write any of it. `hooks/` and `settings.json` are executed by Claude Code wherever it runs, so what a container leaves there runs on the host next time.
168
165
169
166
**Solution:**
170
-
Edit these files on your HOST machine, then restart/rebuild the container.
171
-
172
-
Only `.credentials.json` and `.claude.json` are read-write (needed for auth and state).
167
+
Restore the files from wherever your configuration lives -- keeping `~/.claude` under version control is what makes a change like this visible and reversible. Then look at what put it there: a `postCreateCommand`, a hook, or an agent session in the container all reach that far.
One read-write bind of the whole `~/.claude` directory. No `ro` flag, no per-file mounts. `.credentials.json`, `.claude.json`, `CLAUDE.md`, `settings.json`, `agents/`, `commands/` and `hooks/` are all writable from inside the container, and a write lands on the host's copy.
332
+
333
+
### The Consequence
334
+
`hooks/` and `settings.json` are executed by Claude Code wherever it runs, so content a container writes there runs on the **host** the next time Claude Code starts there -- a `postCreateCommand` from a repository nobody read is enough. The container also holds the live Claude credentials from the mount and, under `dl`, a `GH_TOKEN` carrying repo and workflow scopes. It is not a confidentiality or integrity boundary.
339
335
340
-
### What's Writable (Necessary Risk)
341
-
-`.credentials.json` - OAuth tokens (necessary for auth)
342
-
-`.claude.json` - Setup state (necessary to skip wizard)
336
+
### Why It Is Accepted
337
+
Sharing one config directory is what makes credentials work across the host and every branch container: the access token is short-lived, so a read-only or copied arrangement drifts into a re-auth, and a container that cannot write `.claude.json` re-onboards on every launch. The isolation buys reproducible dependencies and non-colliding concurrent work, not safety against hostile code. `blooop/wayfinder`'s `.devcontainer/devcontainer.json` writes out the same threat model above its `mounts` block.
343
338
344
-
### Mitigation
345
-
- Only use in trusted repositories
346
-
- Files have `600` permissions (user-only access)
347
-
- Container user isolation
348
-
- Regular review of `.claude.json` changes
339
+
Keeping `~/.claude` under version control is the one practical measure here: it makes a change from a container visible instead of silent.
0 commit comments