Skip to content

Commit e6d5392

Browse files
committed
Update claude-code and rtk features to version 2.0.0 and 2.1.0 respectively, enhancing installation paths and dependencies
1 parent 3ae1850 commit e6d5392

8 files changed

Lines changed: 123 additions & 82 deletions

File tree

README.md

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ Published to GHCR under the `sourecode/devcontainer-features` namespace.
99

1010
| Feature | OCI reference | Summary |
1111
|---|---|---|
12-
| `claude-code` | `ghcr.io/sourecode/devcontainer-features/claude-code:1` | Installs the Claude Code CLI via the official native installer (no Node.js required). **Temporary** — will be retired once [anthropics/devcontainer-features#37](https://github.com/anthropics/devcontainer-features/pull/37) is merged. |
13-
| `rtk` | `ghcr.io/sourecode/devcontainer-features/rtk:1` | Installs [rtk](https://github.com/rtk-ai/rtk), an LLM token-reducing CLI proxy; auto-patches Claude Code if present. |
12+
| `claude-code` | `ghcr.io/sourecode/devcontainer-features/claude-code:2` | Installs the Claude Code CLI via the official native installer into `/usr/local/bin`, so the binary survives home-directory volume mounts. Requires Node.js — automatically pulls in the `nvm` feature via `dependsOn`. |
13+
| `rtk` | `ghcr.io/sourecode/devcontainer-features/rtk:2` | Installs [rtk](https://github.com/rtk-ai/rtk), an LLM token-reducing CLI proxy, into `/usr/local/bin`. Auto-patches Claude Code via `postCreateCommand` so the hook is written against the mounted home, not the image. |
1414
| `context-mode` | `ghcr.io/sourecode/devcontainer-features/context-mode:1` | Installs the [`context-mode`](https://github.com/mksglu/context-mode) Claude Code plugin. |
15-
| `nvm` | `ghcr.io/sourecode/devcontainer-features/nvm:2` | Installs [nvm](https://github.com/nvm-sh/nvm) system-wide and optionally a Node version (defaults to LTS), with `node`/`npm`/`npx` symlinked into `/usr/local/bin`. No yarn. |
15+
| `nvm` | `ghcr.io/sourecode/devcontainer-features/nvm:2` | Installs [nvm](https://github.com/nvm-sh/nvm) system-wide at `/usr/local/share/nvm` and optionally a Node version (defaults to LTS), with `node`/`npm`/`npx` symlinked into `/usr/local/bin`. No yarn. |
1616

17-
`rtk` and `context-mode` declare `installsAfter` for both `ghcr.io/sourecode/devcontainer-features/claude-code` and `ghcr.io/anthropics/devcontainer-features/claude-code`, so the runtime orders them after whichever claude-code feature is present.
17+
All binaries land in `/usr/local/bin` (or `/usr/local/share/...`) rather than the user's home, so they survive the shared home-volume pattern described in [`docs/persistence.md`](docs/persistence.md). `rtk` and `context-mode` declare `installsAfter` for both `ghcr.io/sourecode/devcontainer-features/claude-code` and `ghcr.io/anthropics/devcontainer-features/claude-code`, so the runtime orders them after whichever claude-code feature is present.
1818

1919
## Using the features
2020

@@ -25,24 +25,27 @@ image you already use:
2525
{
2626
"image": "debian:trixie-slim",
2727
"features": {
28-
"ghcr.io/sourecode/devcontainer-features/claude-code:1": {},
29-
"ghcr.io/sourecode/devcontainer-features/rtk:1": {
28+
"ghcr.io/sourecode/devcontainer-features/claude-code:2": {},
29+
"ghcr.io/sourecode/devcontainer-features/rtk:2": {
3030
"autoPatchClaude": true
3131
},
3232
"ghcr.io/sourecode/devcontainer-features/context-mode:1": {}
3333
}
3434
}
3535
```
3636

37-
Features run inside the image during build (as root), installing into the
38-
container's `remoteUser` home. After rebuild, the tools are available on the
39-
user's `PATH`.
37+
Features run inside the image during build (as root) and install system-wide
38+
under `/usr/local/`. After rebuild, the tools are available on every user's
39+
`PATH` with no home-directory footprint.
4040

4141
### Feature options
4242

4343
#### `claude-code`
4444

45-
No options. Always installs the latest release.
45+
No options. Always installs the latest release. Declares `dependsOn` for
46+
`ghcr.io/sourecode/devcontainer-features/nvm:2`, so adding `claude-code` to a
47+
devcontainer automatically pulls in `nvm` (and therefore Node.js) even if you
48+
don't list `nvm` yourself.
4649

4750
#### `rtk`
4851

@@ -66,9 +69,9 @@ resolve the correct install order automatically).
6669
### Persisting Claude Code state
6770

6871
Claude login (`~/.claude/.credentials.json`) and chat history (`projects/`,
69-
`sessions/`, `session-env/`) are not persisted across rebuilds by default. See
70-
[`docs/persistence.md`](docs/persistence.md) for mount strategies (host login
71-
bind mount, per-project credentials, or a shared named volume).
72+
`sessions/`, `session-env/`) live in the user's home. Persist them by mounting
73+
`$HOME` as a named volume — see [`docs/persistence.md`](docs/persistence.md)
74+
for the shared-home pattern we use across every devcontainer.
7275

7376
## Developing on this repo
7477

@@ -91,6 +94,7 @@ src/
9194
devcontainer-feature.json
9295
install.sh
9396
docs/
97+
migration-guide.md
9498
persistence.md
9599
```
96100

@@ -100,9 +104,21 @@ that runs as `root` inside the container during the build.
100104

101105
### Writing an install.sh
102106

103-
- `install.sh` starts as `root`. To land files in the remote user's home,
104-
resolve the user via `_REMOTE_USER` / `_REMOTE_USER_HOME` (set by the
105-
devcontainer runtime) and `su - "$USER"` for user-scoped steps.
107+
- `install.sh` starts as `root`. **Prefer system-wide install paths**
108+
(`/usr/local/bin`, `/usr/local/share/<id>`, `/etc/profile.d`) over anything
109+
under the remote user's home. The shared-home volume pattern
110+
([`docs/persistence.md`](docs/persistence.md)) means writes into
111+
`/home/<user>` at build time only appear on first-create and then get
112+
shadowed by the named volume on every subsequent run.
113+
- If a tool's upstream installer insists on writing to `$HOME`, run it under
114+
a scratch `HOME` (`mktemp -d`) and relocate the resulting binary to
115+
`/usr/local/bin` (see `src/claude-code/install.sh`). If the tool supports
116+
an override env var (e.g. `RTK_INSTALL_DIR`), pass it directly.
117+
- For anything that genuinely needs to live in the user's real home
118+
(credentials, plugin state, shell-rc tweaks), emit a script to
119+
`/usr/local/share/<id>/post-create.sh` and wire it via `postCreateCommand`
120+
in `devcontainer-feature.json` so it runs against the mounted home, not
121+
the image.
106122
- Feature options are exposed as **uppercased** environment variables (e.g.
107123
option `autoPatchClaude``$AUTOPATCHCLAUDE`). Always apply a default:
108124
`"${FOO:-true}"`.
@@ -111,9 +127,9 @@ that runs as `root` inside the container during the build.
111127
- Don't assume the base image has any particular tools — install `curl`,
112128
`ca-certificates`, etc. from `apt-get` if absent. Keep installs idempotent
113129
where reasonable.
114-
- Declare dependencies with `installsAfter` so the runtime orders features
115-
correctly (e.g. `rtk` and `context-mode` list both the `sourecode` and
116-
`anthropics` claude-code feature IDs so either ordering target works).
130+
- Use `installsAfter` for soft ordering (e.g. `rtk` lists both the `sourecode`
131+
and `anthropics` claude-code IDs), and `dependsOn` for hard requirements
132+
that should auto-pull another feature (e.g. `claude-code``nvm`).
117133

118134
### Testing a feature locally
119135

docs/migration-guide.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,9 @@ Typical `features` block for a C++ project:
158158
"ghcr.io/sourecode/devcontainer-features/cmake:1": {},
159159
"ghcr.io/sourecode/devcontainer-features/llvm:1": {},
160160
"ghcr.io/sourecode/devcontainer-features/sccache:1": {},
161-
"ghcr.io/sourecode/devcontainer-features/claude-code:1": {},
162-
"ghcr.io/sourecode/devcontainer-features/rtk:1": {},
161+
"ghcr.io/sourecode/devcontainer-features/nvm:2": {},
162+
"ghcr.io/sourecode/devcontainer-features/claude-code:2": {},
163+
"ghcr.io/sourecode/devcontainer-features/rtk:2": {},
163164
"ghcr.io/sourecode/devcontainer-features/context-mode:1": {}
164165
}
165166
```
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
22
"id": "claude-code",
3-
"version": "1.0.0",
3+
"version": "2.0.0",
44
"name": "claude-code",
5-
"description": "TEMPORARY: Installs the latest Claude Code CLI via the official native installer (no Node.js required). Drop this feature in favor of `ghcr.io/anthropics/devcontainer-features/claude-code` once https://github.com/anthropics/devcontainer-features/pull/37 is merged.",
6-
"documentationURL": "https://github.com/SoureCode/devcontainer-features/tree/master/src/claude-code"
5+
"description": "Installs the latest Claude Code CLI via the official native installer, placing the binary in /usr/local/bin so it survives home-directory volume mounts.",
6+
"documentationURL": "https://github.com/SoureCode/devcontainer-features/tree/master/src/claude-code",
7+
"dependsOn": {
8+
"ghcr.io/sourecode/devcontainer-features/nvm:2": {}
9+
}
710
}

src/claude-code/install.sh

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
11
#!/usr/bin/env bash
2-
# claude-code feature installer. TEMPORARY — remove this feature in favor of
3-
# ghcr.io/anthropics/devcontainer-features/claude-code once
4-
# https://github.com/anthropics/devcontainer-features/pull/37 is merged.
2+
# claude-code feature installer.
3+
#
4+
# Requires Node.js on PATH (provided by the nvm feature via `dependsOn`).
5+
#
6+
# The official installer writes to $HOME/.local/bin. Because our devcontainers
7+
# mount $HOME as a named volume (see docs/persistence.md), anything dropped in
8+
# the user's home at build time is hidden on any run where the volume already
9+
# has content. Install into a scratch HOME and relocate the binary to
10+
# /usr/local/bin so it lives in image layers, outside the mount.
511
set -e
612

7-
USER_NAME="${_REMOTE_USER:-${USERNAME:-root}}"
8-
if [ "$USER_NAME" = "root" ]; then
9-
USER_HOME="/root"
10-
else
11-
USER_HOME="$(getent passwd "$USER_NAME" | cut -d: -f6)"
12-
fi
13-
1413
if ! command -v curl >/dev/null 2>&1; then
1514
apt-get update
1615
apt-get install -y --no-install-recommends curl ca-certificates
1716
rm -rf /var/lib/apt/lists/*
1817
fi
1918

20-
run_as_user() {
21-
if [ "$USER_NAME" = "root" ]; then
22-
bash -c "$1"
23-
else
24-
su - "$USER_NAME" -c "$1"
25-
fi
26-
}
19+
SCRATCH="$(mktemp -d)"
20+
trap 'rm -rf "$SCRATCH"' EXIT
2721

28-
run_as_user 'curl -fsSL https://claude.ai/install.sh | bash'
22+
HOME="$SCRATCH" curl -fsSL https://claude.ai/install.sh | HOME="$SCRATCH" bash
2923

30-
CLAUDE_BIN="$USER_HOME/.local/bin/claude"
31-
if [ -x "$CLAUDE_BIN" ]; then
32-
ln -sf "$CLAUDE_BIN" /usr/local/bin/claude
33-
else
34-
echo "claude-code feature: expected $CLAUDE_BIN after install, but it was not found." >&2
24+
SRC_BIN="$SCRATCH/.local/bin/claude"
25+
if [ ! -x "$SRC_BIN" ]; then
26+
echo "claude-code feature: expected $SRC_BIN after install, but it was not found." >&2
3527
exit 1
3628
fi
29+
30+
install -m 0755 "$SRC_BIN" /usr/local/bin/claude

src/nvm/devcontainer-feature.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "nvm",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"name": "nvm",
55
"description": "Installs nvm (Node Version Manager) system-wide at /usr/local/share/nvm and optionally a Node version (defaults to LTS), with node/npm/npx symlinked into /usr/local/bin so every shell and subsequent feature sees them. No yarn.",
66
"documentationURL": "https://github.com/SoureCode/devcontainer-features/tree/master/src/nvm",

src/nvm/install.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ export NVM_DIR="$NVM_DIR"
3535
EOF
3636
chmod 644 /etc/profile.d/nvm.sh
3737

38+
# Non-login interactive bash shells (VS Code / code-server terminals) source
39+
# /etc/bash.bashrc, not /etc/profile.d. Without this hook, the `nvm` shell
40+
# function isn't defined in those shells even though `node` / `npm` work via
41+
# the /usr/local/bin symlinks below. Idempotent guard so re-running the
42+
# feature install doesn't duplicate the block.
43+
if [ ! -f /etc/bash.bashrc ] || ! grep -q 'nvm-feature-hook' /etc/bash.bashrc; then
44+
cat >>/etc/bash.bashrc <<'EOF'
45+
46+
# nvm-feature-hook: sourced by non-login interactive bash shells.
47+
if [ -z "${NVM_DIR:-}" ] && [ -s /etc/profile.d/nvm.sh ]; then
48+
. /etc/profile.d/nvm.sh
49+
fi
50+
EOF
51+
fi
52+
53+
# Same for zsh if the distro ships /etc/zsh/zshrc.
54+
if [ -f /etc/zsh/zshrc ] && ! grep -q 'nvm-feature-hook' /etc/zsh/zshrc; then
55+
cat >>/etc/zsh/zshrc <<'EOF'
56+
57+
# nvm-feature-hook: sourced by interactive zsh shells.
58+
if [ -z "${NVM_DIR:-}" ] && [ -s /etc/profile.d/nvm.sh ]; then
59+
. /etc/profile.d/nvm.sh
60+
fi
61+
EOF
62+
fi
63+
3864
if [ "$NODE_VERSION" != "none" ]; then
3965
if [ "$NODE_VERSION" = "lts" ]; then
4066
NVM_INSTALL_ARG="--lts"

src/rtk/devcontainer-feature.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
{
22
"id": "rtk",
3-
"version": "1.0.2",
3+
"version": "2.0.0",
44
"name": "rtk",
5-
"description": "Installs rtk, an LLM token-reducing CLI proxy, and optionally wires its auto-rewrite hook into Claude Code.",
5+
"description": "Installs rtk, an LLM token-reducing CLI proxy, and optionally wires its auto-rewrite hook into Claude Code via postCreateCommand.",
66
"documentationURL": "https://github.com/SoureCode/devcontainer-features/tree/master/src/rtk",
77
"options": {
88
"autoPatchClaude": {
99
"type": "boolean",
1010
"default": true,
11-
"description": "Run `rtk init -g --auto-patch` to wire the auto-rewrite hook into Claude Code (requires a claude-code feature)."
11+
"description": "Run `rtk init -g --auto-patch` post-create to wire the auto-rewrite hook into Claude Code (requires a claude-code feature)."
1212
}
1313
},
14+
"postCreateCommand": "/usr/local/share/rtk/post-create.sh",
1415
"installsAfter": [
1516
"ghcr.io/sourecode/devcontainer-features/claude-code",
1617
"ghcr.io/anthropics/devcontainer-features/claude-code"

src/rtk/install.sh

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
11
#!/usr/bin/env bash
22
# rtk feature installer.
33
# https://github.com/rtk-ai/rtk
4+
#
5+
# Our devcontainers mount $HOME as a named volume (see docs/persistence.md),
6+
# so anything written to the user's home at build time is hidden on runs
7+
# where the volume already has contents. Install the binary system-wide to
8+
# /usr/local/bin, and defer the `rtk init` auto-patch to postCreateCommand
9+
# so it runs after the mount against the real home.
410
set -e
511

612
AUTO_PATCH_CLAUDE="${AUTOPATCHCLAUDE:-true}"
713

8-
USER_NAME="${_REMOTE_USER:-${USERNAME:-root}}"
9-
USER_HOME="${_REMOTE_USER_HOME:-}"
10-
if [ -z "$USER_HOME" ]; then
11-
if [ "$USER_NAME" = "root" ]; then
12-
USER_HOME="/root"
13-
else
14-
USER_HOME="$(getent passwd "$USER_NAME" | cut -d: -f6)"
15-
fi
16-
fi
17-
1814
if ! command -v curl >/dev/null 2>&1; then
1915
apt-get update
2016
apt-get install -y --no-install-recommends curl ca-certificates
2117
rm -rf /var/lib/apt/lists/*
2218
fi
2319

24-
run_as_user() {
25-
if [ "$USER_NAME" = "root" ]; then
26-
bash -c "$1"
27-
else
28-
su - "$USER_NAME" -c "$1"
29-
fi
30-
}
31-
32-
run_as_user 'curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh'
33-
34-
RTK_BIN="$USER_HOME/.local/bin/rtk"
35-
if [ -x "$RTK_BIN" ]; then
36-
ln -sf "$RTK_BIN" /usr/local/bin/rtk
37-
else
38-
echo "rtk feature: expected $RTK_BIN after install, but it was not found." >&2
20+
curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh |
21+
RTK_INSTALL_DIR=/usr/local/bin sh
22+
23+
if [ ! -x /usr/local/bin/rtk ]; then
24+
echo "rtk feature: expected /usr/local/bin/rtk after install, but it was not found." >&2
3925
exit 1
4026
fi
4127

42-
if [ "$AUTO_PATCH_CLAUDE" = "true" ]; then
43-
if command -v claude >/dev/null 2>&1; then
44-
run_as_user 'rtk init -g --auto-patch'
45-
else
46-
echo "rtk feature: claude CLI not on PATH, skipping auto-patch. Install a claude-code feature (e.g. ghcr.io/sourecode/devcontainer-features/claude-code) to enable it." >&2
47-
fi
28+
mkdir -p /usr/local/share/rtk
29+
cat >/usr/local/share/rtk/post-create.sh <<EOF
30+
#!/usr/bin/env bash
31+
# Written by the rtk devcontainer feature at build time.
32+
# Runs as the remote user via postCreateCommand so it can safely write to
33+
# the (volume-mounted) home directory.
34+
set -e
35+
36+
if [ "${AUTO_PATCH_CLAUDE}" != "true" ]; then
37+
exit 0
38+
fi
39+
40+
if ! command -v claude >/dev/null 2>&1; then
41+
echo "rtk feature: claude CLI not on PATH, skipping auto-patch. Install a claude-code feature (e.g. ghcr.io/sourecode/devcontainer-features/claude-code) to enable it." >&2
42+
exit 0
4843
fi
44+
45+
mkdir -p "\$HOME/.claude"
46+
rtk init -g --auto-patch
47+
EOF
48+
chmod 0755 /usr/local/share/rtk/post-create.sh

0 commit comments

Comments
 (0)