Skip to content

feat(devbase): stop installing the Claude Code CLI - #13

Merged
KaiSchwarz-cnic merged 2 commits into
mainfrom
RSRMID-3053/drop-claude-cli-dependency
Sep 9, 2026
Merged

feat(devbase): stop installing the Claude Code CLI#13
KaiSchwarz-cnic merged 2 commits into
mainfrom
RSRMID-3053/drop-claude-cli-dependency

Conversation

@KaiSchwarz-cnic

Copy link
Copy Markdown
Contributor

RSRMID-3053 — drops ghcr.io/anthropics/devcontainer-features/claude-code:1 from devbase's dependsOn. This is a major, released as 2.0.0.

Why

The npm CLI was redundant. The VS Code extension ships and runs its own runtime — verified by process list in a running container, not inferred:

/home/vscode/.vscode-server/extensions/anthropic.claude-code-2.1.263-linux-x64/resources/native-binary/claude --output-format stream...

The extension never invokes the npm global at /usr/local/share/nvm/versions/node/v24.20.0/bin/claude. The extension is what we use, so the npm install was a second copy of the same thing.

And it could not update itself. The claude-code feature installs as root into a global npm tree the node feature makes vscode:nvm, so the package directory lands root-owned at mode 755 — no group write — and claude update fails for the remote user until someone runs by hand:

sudo chown -R vscode:nvm "$(npm root -g)/@anthropic-ai"

The alternative was considered and rejected. Chowning that directory in install.sh would have been a fix(devbase) — a patch, reaching consumers on their next rebuild with no migration. It keeps a redundant second runtime in every image purely to make it self-updating. Removal is the smaller end state, and this Feature has exactly one consumer group, so a major costs less here than it would for a public Feature.

Checked before removing: no repository's scripts, workflows or package manifests call the claude CLI — zero hits across the fleet.

What deliberately stays

  • RTK. Its PreToolUse hook lives in the bind-mounted, host-shared ~/.claude/settings.json and fires under the extension's runtime exactly as it did under the CLI's — confirmed by RTK-filtered command output in a container running only the extension. Without the binary that hook still exits 127 on every Bash call, which is the whole reason RTK is in this Feature.
  • node in dependsOn. One of its two documented reasons retires here (claude-code installed EOL Node 18 from nodesource when it found no Node); the other stands unchanged — devbase's own pnpm, commitizen and npm-floor steps need npm.
  • The anthropic.claude-code and anthropic.claude-vscode VS Code extensions, now the only Claude Code in a container.

Tests move with the behaviour

  • dependsOn installed the claude CLI inverts to assert the CLI is absent. A removal with no assertion cannot distinguish "removed" from "quietly came back through some other feature's own dependsOn" — which is exactly what dependsOn does.
  • node_pinned's claude-code added no Node of its own nodesource guard is dropped, not kept. Its subject is gone, so it could no longer fail, and per this repo's own doctrine a check that cannot fail is worse than no check. The comment explaining why is left in its place.

Ordering, and the follow-up

The default .devcontainer/devcontainer.json still references devbase:1, per the dogfooding rule — a frame stops expecting something only after the release that stops providing it. Its comment now says so explicitly. The local/ frame builds the working-tree copy, so the removal takes effect there immediately; that asymmetry is noted, since it is the first place anyone will see claude missing.

Consumers pin :1, so 2.0.0 reaches nobody on a rebuild. The follow-up sweep has to move each frame from devbase:1 to devbase:2 and re-resolve the eight committed devcontainer-lock.json files, which neither devcontainer upgrade nor Dependabot will move. README.md gains a Migrating to 2.0.0 section covering exactly that, plus how to opt back into the CLI per repository if a container ever genuinely needs it.

Note node-sdk keeps the CLI regardless — it lists claude-code in its own frame, because its devbase migration was never completed (a separate finding from RSRMID-3052, still unticketed).

Verification

pnpm features:lint and pnpm prettier clean. The container suite runs here in CI, since features/** is touched — that is what exercises the inverted assertion, as this workspace container has no Docker daemon.

devbase declared ghcr.io/anthropics/devcontainer-features/claude-code:1 in
dependsOn, so every consuming container installed the Claude Code CLI globally
via npm. That copy was redundant, and it was broken in a way that needed a
manual fix.

Redundant, because the VS Code extension ships and runs its own runtime.
Verified by process list in a running container rather than inferred:

  /home/vscode/.vscode-server/extensions/anthropic.claude-code-2.1.263-linux-x64/
    resources/native-binary/claude --output-format stream...

The extension never invokes the npm global. It is what we actually use, so the
npm install was a second copy of the same thing.

Broken, because the claude-code feature installs as root into a global npm tree
that the node feature makes vscode:nvm. The package directory therefore lands
root-owned at mode 755 — no group write — and `claude update` fails for the
remote user until someone runs, by hand:

  sudo chown -R vscode:nvm "$(npm root -g)/@Anthropic-AI"

Fixing the ownership in install.sh was the alternative, and it would have
shipped as a patch and so reached consumers immediately. Rejected: it keeps a
redundant second runtime in every image purely to make it self-updating.
Removing it is the smaller end state, and this Feature has exactly one consumer
group, so a major costs less here than it would for a public Feature.

Checked before removing: no repository's scripts, workflows or package manifests
call the claude CLI — zero hits across the fleet.

Three things deliberately stay.

RTK stays. Its PreToolUse hook lives in the bind-mounted, host-shared
~/.claude/settings.json and fires under the extension's runtime exactly as it
did under the CLI's — confirmed by RTK-filtered command output in a container
running only the extension. Without the binary that hook still exits 127 on
every Bash call, which is the whole reason RTK is in this Feature.

node stays in dependsOn. One of its two documented reasons retires here —
claude-code installed EOL Node 18 from nodesource when it found no Node — but
the other stands unchanged: devbase's own pnpm, commitizen and npm-floor steps
need npm.

The anthropic.claude-code and anthropic.claude-vscode VS Code extensions stay.
They are now the only Claude Code in a container.

Tests move with the behaviour. The "dependsOn installed the claude CLI" check
inverts to assert the CLI is absent: a removal with no assertion cannot
distinguish "removed" from "quietly came back through some other feature's own
dependsOn", which is exactly what dependsOn does. node_pinned's nodesource
guard is dropped rather than kept — its subject is gone, so it could no longer
fail, and a check that cannot fail is worse than no check.

BREAKING CHANGE: devbase no longer installs the Claude Code CLI. A container
built from 2.x has no `claude` on PATH unless the repository lists
ghcr.io/anthropics/devcontainer-features/claude-code:1 itself. The Claude Code
VS Code extension is unaffected — it runs its own bundled runtime. Consumers
pin devbase:1, so this reaches nobody until they change that reference to
devbase:2 and re-resolve any committed devcontainer-lock.json; see "Migrating
to 2.0.0" in README.md.

Refs RSRMID-3053.
Both frames said node, github-cli and claude-code all come from devbase's
dependsOn. The preceding commit removed claude-code, so that is now wrong in
three places.

The default frame keeps referencing devbase:1, per the dogfooding rule — the
frame only stops expecting something after the release that stops providing it —
so its comment says so explicitly: the CLI is still there until that reference
moves to :2, and deliberately gone afterwards.

The local frame has no published :1 to fall back on: it builds the working-tree
copy, so the removal takes effect there immediately. Noted, because that
asymmetry is the whole point of having the alternate config and is the first
place anyone will notice `claude` missing.

Also corrects the ~/.claude mkdir rationale, which named claude-code as the
thing that could not write its config into a root-owned directory. The
directory is still needed for exactly that reason — the VS Code extension
writes there, and the RTK hook is read from the settings.json inside it.

Refs RSRMID-3053.
@KaiSchwarz-cnic
KaiSchwarz-cnic merged commit 4df7d0f into main Sep 9, 2026
9 checks passed
@KaiSchwarz-cnic
KaiSchwarz-cnic deleted the RSRMID-3053/drop-claude-cli-dependency branch September 9, 2026 07:29
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.0.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant