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
{{ message }}
Repository navigation
Commit cbff39a
Browse filesBrowse the repository at this point in the historyBrowse files
docs: correct install mechanism and remove the installsAfter claim
The .devcontainer tree is about to be copied into 26 repos, so every
factual statement in it had to be checked against the code beside it.
Part 1, both verified against install.sh and devcontainer-feature.json:
- The CLI is not the @anthropic-ai/claude-code npm package. install.sh
runs `pixi global install --channel https://prefix.dev/blooop
claude-shim`, downloading pixi to /usr/local/bin/pixi first if the
image does not carry it. The blooop prefix.dev channel is therefore a
dependency of this feature, which the README now says. install.sh only
checks that the trampoline exists, so the binary arrives on the first
`claude` run -- also now stated.
- Removed the claim that Node.js is installed via `installsAfter`, in
both places. devcontainer-feature.json declares no `installsAfter` key
at all, and nothing node-related is installed because the CLI is a
pixi package. The Container requirements bullet names pixi instead;
nothing was invented to replace the mechanism.
What the sweep found beyond that:
- init-host.sh creates only ~/.claude, so "they will be created if they
don't exist" was false for the subdirectories and files listed under
it, and the mount being one directory bind means a missing
subdirectory produces no mount warnings. Corrected in the README
Requirements section, its "Mount warnings about missing files"
troubleshooting entry, and the TROUBLESHOOTING quick reference.
- install.sh runs at image build time, so it never touches the host. The
Security Notes claim that its 600 credential files "keeps other users
on the host out" was wrong; the files it creates live in the image and
the bind mount covers them at runtime. Same correction to "Creates
.claude/ structure in the container".
- Authentication "First-Time Setup" still told you to run the OAuth flow
inside the container, which the same file says three times cannot
complete on the default bridge network. It now points at the host.
- Two "rebuild the container" steps after editing ~/.claude on the host:
the mount is live, so restarting `claude` is enough.
- ci/devcontainer.json declares "../claude-code", not "./claude-code" --
fixed in three places.
- The standalone `sudo ./install.sh` test is a container operation; on a
host with no /home/vscode it exits with an error.
Copy file name to clipboardExpand all lines: .devcontainer/claude-code/README.md
+20-21Lines changed: 20 additions & 21 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,14 +6,14 @@ A local Dev Container Feature that installs the Claude Code CLI and bind-mounts
6
6
7
7
This feature combines two capabilities:
8
8
9
-
1.**CLI Installation**: Installs the `@anthropic-ai/claude-code` npm package globally
9
+
1.**CLI Installation**: `install.sh` runs `pixi global install --channel https://prefix.dev/blooop claude-shim`, and downloads pixi to `/usr/local/bin/pixi` first if the base image does not already carry it
10
10
2.**Configuration Mounting**: Bind-mounts your host machine's `~/.claude` directory into the container, read-write
11
11
12
12
## What Gets Installed
13
13
14
-
-**Claude Code CLI**: The `claude` command becomes available in your container
14
+
-**Claude Code CLI**: The `claude` command becomes available in your container. It comes from the `claude-shim` package on the `blooop` prefix.dev channel, so that channel is a dependency of this feature. `install.sh` checks only that the pixi trampoline exists -- the binary it points at is downloaded on the first `claude` run.
15
15
-**VS Code Extension**: Automatically installs the `anthropic.claude-code` extension
16
-
-**Configuration Directories**: Creates `.claude/`structure in the container
16
+
-**Configuration Directories**: `install.sh` creates the `.claude/`tree, though it does so while the image is built -- at runtime the host's bind mount covers it
17
17
18
18
## What Gets Mounted
19
19
@@ -71,8 +71,6 @@ CI publishes that image, and the `devcontainer.json` every branch launches from
71
71
72
72
Declaring the feature a second time there makes the devcontainer spec build a derived image on the first launch of every branch, reinstalling what the pulled image already carries.
73
73
74
-
**Note**: Node.js is automatically installed via the `installsAfter` dependency mechanism - you don't need to explicitly add it to your features.
75
-
76
74
### Authentication Uses Host Credentials, Not Host Networking
77
75
78
76
No OAuth flow runs inside the container. You authenticate `claude` once on the host, and the `~/.claude` bind mount plus `CLAUDE_CONFIG_DIR=/home/vscode/.claude` point the container at those same credentials, refresh tokens included. Every container of every branch reads them, and nothing has to reach the host's network to do it.
@@ -109,7 +107,7 @@ With VS Code:
109
107
110
108
### Host Machine
111
109
112
-
You should have these files/directories on your host machine (they will be created if they don't exist):
110
+
`init-host.sh` runs on the host as the `initializeCommand` and creates `~/.claude` if it is missing. Nothing creates the contents below; they are optional, and the container starts without them:
113
111
114
112
```bash
115
113
~/.claude/
@@ -120,7 +118,7 @@ You should have these files/directories on your host machine (they will be creat
120
118
└── hooks/ # Optional: event hooks
121
119
```
122
120
123
-
**Note**: If these don't exist on your host, the container will still build successfully, but you may see mount warnings. You can create them with:
121
+
**Note**: The mount is the `~/.claude` directory itself, so a missing subdirectory or file costs nothing at launch. To create them anyway:
124
122
125
123
```bash
126
124
mkdir -p ~/.claude/{agents,commands,hooks}
@@ -130,7 +128,7 @@ touch ~/.claude/settings.json
130
128
131
129
### Container
132
130
133
-
-**Node.js 18+** and **npm** are automatically installed via the `installsAfter` dependency mechanism
131
+
-**pixi**, which the `Dockerfile` installs to `/usr/local/bin/pixi`; `install.sh` downloads it there itself if it is missing
You can test the install script standalone, from inside the container:
167
165
168
166
```bash
169
167
cd .devcontainer/claude-code
170
168
sudo ./install.sh
171
169
```
172
170
171
+
It resolves its target from `_REMOTE_USER` and `_REMOTE_USER_HOME` and falls back to `vscode`, so on a host with no `/home/vscode` it exits with an error rather than doing anything.
172
+
173
173
### Debugging
174
174
175
175
Check if Claude is installed:
@@ -196,12 +196,12 @@ The write test uses a throwaway file on purpose. Do not test the mount by append
196
196
### How It Works
197
197
198
198
1.**Already Authenticated on Host**: If you have Claude Code set up on your host machine, credentials are automatically shared with the container
199
-
2.**First-Time Setup**: Run `claude`in the container and follow the OAuth flow:
200
-
- The CLI will provide an OAuth URL
201
-
- Open the URL in your browser (on your host machine)
199
+
2.**First-Time Setup**: Run `claude`on the **host** and follow the OAuth flow there:
200
+
- The CLI provides an OAuth URL
201
+
- Open the URL in your browser
202
202
- Click "Authorize"
203
-
- The callback should complete automatically, or you may need to paste the code
204
-
- Credentials are saved to `~/.claude/.credentials.json` on your host
203
+
- The callback completes, because the CLI and the browser are both on the host
204
+
- Credentials are saved to `~/.claude/.credentials.json`, and the mount carries them into every container
205
205
206
206
### OAuth Callback Behavior
207
207
@@ -214,7 +214,7 @@ The OAuth flow opens a local callback server. In containers, this can behave dif
214
214
215
215
**"Paste code here" prompt hangs forever:**
216
216
- Check that `~/.claude/.credentials.json` exists on your host with proper permissions (`600`)
217
-
- Try authenticating on your host machine first, then rebuild the container
217
+
- Try authenticating on your host machine first, then restart `claude` in the container -- the mount is live, so no rebuild is needed
218
218
- If the callback fails, look for the authorization code in the URL after clicking "Authorize"
# Then restart `claude` in the container -- the mount is live, so no rebuild is needed
241
240
```
242
241
243
242
**Root cause:** Claude tracks setup wizard completion per-workspace in `.claude.json` under `.projects["/workspaces/pythontemplate"].projectOnboardingSeenCount`. When this is `0`, the setup wizard runs. Set it to `1` to mark setup as complete.
@@ -310,7 +309,7 @@ Users would then reference it as:
310
309
}
311
310
```
312
311
313
-
Instead of `"./claude-code": {}`
312
+
Instead of `"../claude-code": {}`
314
313
315
314
## Optional: Future Composition
316
315
@@ -397,9 +396,9 @@ Then use both:
397
396
2. **Authenticate on host**, mount credentials, remove runArgs (no OAuth needed in container)
398
397
3. **Manually install extensions** after container starts
399
398
400
-
### Mount warnings about missing files
399
+
### `~/.claude` missing on the host
401
400
402
-
**Solution**: Create the directories on your host:
401
+
**Solution**: The `initializeCommand` (`init-host.sh`) creates it before the container starts. To lay out the rest yourself:
403
402
404
403
```bash
405
404
mkdir -p ~/.claude/{agents,commands,hooks}
@@ -415,7 +414,7 @@ The whole `~/.claude` directory is bind-mounted read-write as one mount, so noth
415
414
- **`.claude.json`**: account info, user ID, per-workspace onboarding and trust state
416
415
- **`CLAUDE.md`**, **`settings.json`**, **`agents/`**, **`commands/`**, **`hooks/`**: the host's copies, in place
417
416
418
-
`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.
417
+
`install.sh`runs when the image is built, so the two `600` credential files it creates live in the image and the bind mount covers them at runtime. Permissions on the host's real files are whatever the host set -- see Issue 6 in TROUBLESHOOTING.md. Nothing here restricts code running inside the container, which runs as the user those files belong to.
419
418
420
419
### The Consequence Worth Naming
421
420
`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.
Copy file name to clipboardExpand all lines: .devcontainer/claude-code/TROUBLESHOOTING.md
+6-5Lines changed: 6 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,9 @@
2
2
3
3
## Quick Reference
4
4
5
-
### Files That Must Exist on Host
5
+
### Files on the Host
6
+
7
+
`init-host.sh` creates `~/.claude` as the `initializeCommand`. Everything inside it is optional, and only `.credentials.json` is load-bearing for an authenticated `claude`.
6
8
7
9
```bash
8
10
~/.claude/ # one bind mount, read-write, shared with every container
@@ -34,7 +36,7 @@ Every one of these is writable from inside the container, and a write lands on t
34
36
}
35
37
```
36
38
37
-
The `./claude-code` feature is declared in `.devcontainer/ci/devcontainer.json`, the config CI builds the image from, so there is no `features` block here -- and no `runArgs` either.
39
+
The `../claude-code` feature is declared in `.devcontainer/ci/devcontainer.json`, the config CI builds the image from, so there is no `features` block here -- and no `runArgs` either.
# Then restart `claude` in the container -- the mount is live, so no rebuild is needed
73
74
```
74
75
75
76
**Why 999?** The field is `projectOnboardingSeenCount` - it increments each time you see the wizard. Setting it high tells Claude "this workspace has been onboarded many times, skip the wizard."
0 commit comments