Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ Installs the AI coding agents we standardize on:
- **GitHub CLI** (`gh`, via `dependsOn` on the official
`ghcr.io/devcontainers/features/github-cli` feature)

### Persistent `gh` login
### Persistent Claude + `gh` logins

The feature mounts a named volume (`devcontainer-gh-config`) at
`/home/node/.config/gh`, so a `gh auth login` survives container **rebuilds** — you log
in once instead of after every rebuild. A `postCreateCommand` `chown`s the volume so the
`node` user can write to it.
The feature mounts named volumes (`devcontainer-claude-config` at `/home/node/.claude`,
`devcontainer-gh-config` at `/home/node/.config/gh`) and sets `CLAUDE_CONFIG_DIR`, so the
Claude and `gh auth login` sessions survive container **rebuilds** — you log in once
instead of after every rebuild. A `postCreateCommand` `chown`s the volumes so the `node`
user can write to them.

> The volume is **shared across all repos** that use this feature (gh auth is account-level,
> not repo-level), so logging in from one container carries over to the others. The mount
> path assumes the `node` remote user (the base image we standardize on).
> The volumes are **shared across all repos** that use this feature (these logins are
> account-level, not repo-level), so logging in from one container carries over to the
> others. The mount paths assume the `node` remote user (the base image we standardize on).

### Usage

Expand All @@ -38,10 +39,31 @@ In any repo's `.devcontainer/devcontainer.json`:
}
```

This single line replaces the official `claude-code` and `github-cli` feature lines *and*
the inline `npm install -g @openai/codex` in `post-create.sh`.
This single line replaces the official `claude-code` and `github-cli` feature lines, the
inline `npm install -g @openai/codex`, **and** the `.claude` volume / `CLAUDE_CONFIG_DIR` /
`chown` wiring that otherwise lives in each repo's `devcontainer.json` + `post-create.sh`.

### Updating the agent versions everywhere
## `pnpm`

Sets up [pnpm](https://pnpm.io) via [Corepack](https://github.com/nodejs/corepack):

- Runs `corepack enable` (adds the `pnpm` shim) at build time.
- At `postCreate`, runs `corepack install` to pin the pnpm version from the workspace's
`package.json` `packageManager` field.
- Removes the `npm`/`npx` binaries (after build-time installs have run) to enforce
pnpm-only usage. This is the default; set `removeNpm: false` to keep npm available.

```jsonc
"features": {
"ghcr.io/rocicorp/devcontainer-features/pnpm:1": {}
}
```

This replaces the corepack/pnpm/npm-removal block that otherwise lives in each repo's
`post-create.sh`. Combined with `agents`, a consumer repo's `devcontainer.json` needs no
lifecycle scripts at all.

## Updating the feature versions everywhere

1. Bump `codexVersion` default (and/or the `dependsOn` claude-code pin) in
`src/agents/devcontainer-feature.json`, raise the feature `version`, merge to `main`.
Expand Down
14 changes: 11 additions & 3 deletions src/agents/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"id": "agents",
"version": "1.1.0",
"version": "1.2.0",
"name": "AI Coding Agents (Codex + Claude Code + GitHub CLI)",
"description": "Installs the OpenAI Codex CLI, Claude Code, and the GitHub CLI so every dev container ships with the agents pre-installed. Persists the `gh` login across rebuilds via a named volume.",
"description": "Installs the OpenAI Codex CLI, Claude Code, and the GitHub CLI so every dev container ships with the agents pre-installed. Persists the Claude and `gh` logins across rebuilds via named volumes.",
"documentationURL": "https://github.com/rocicorp/devcontainer-features/tree/main/src/agents",
"options": {
"codexVersion": {
Expand All @@ -15,13 +15,21 @@
"ghcr.io/anthropics/devcontainer-features/claude-code:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"containerEnv": {
"CLAUDE_CONFIG_DIR": "/home/node/.claude"
},
"mounts": [
{
"source": "devcontainer-gh-config",
"target": "/home/node/.config/gh",
"type": "volume"
},
{
"source": "devcontainer-claude-config",
"target": "/home/node/.claude",
"type": "volume"
}
],
"postCreateCommand": "sudo chown -R node:node /home/node/.config/gh 2>/dev/null || true",
"postCreateCommand": "sudo chown -R node:node /home/node/.config/gh /home/node/.claude 2>/dev/null || true",
"installsAfter": ["ghcr.io/devcontainers/features/node"]
}
16 changes: 16 additions & 0 deletions src/pnpm/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "pnpm",
"version": "1.0.0",
"name": "pnpm via Corepack",
"description": "Enables Corepack and installs the pnpm version pinned in the workspace's package.json `packageManager` field. Optionally removes npm/npx to enforce pnpm.",
"documentationURL": "https://github.com/rocicorp/devcontainer-features/tree/main/src/pnpm",
"options": {
"removeNpm": {
"type": "boolean",
"default": true,
"description": "Remove the npm and npx binaries after setup to enforce pnpm-only usage. Set to false to keep npm available."
}
},
"postCreateCommand": "/usr/local/share/rocicorp-pnpm/post-create.sh",
"installsAfter": ["ghcr.io/devcontainers/features/node"]
}
39 changes: 39 additions & 0 deletions src/pnpm/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail

# Option (booleans arrive as the strings "true"/"false").
REMOVE_NPM="${REMOVENPM:-true}"

if ! command -v corepack >/dev/null 2>&1; then
echo "ERROR: corepack not found. This feature needs a Node.js install (base image or node feature)." >&2
exit 1
fi

echo "Enabling Corepack..."
corepack enable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this causes .pnpm-store/ folder to be created in the repo dir?

Image

Here's how we fixed in cloudzero repo: https://github.com/rocicorp/cloudzero/blob/5c10238ad6cd2368b70c35e345d0803482ee0e13/.devcontainer/post-create.sh#L4-L10


# `corepack install` (pins pnpm from the workspace packageManager field) and the optional
# npm removal both have to run at postCreate, not here:
# - the workspace isn't mounted during the build, so package.json isn't readable yet;
# - npm must survive until other features (e.g. global npm installs) have run at build time.
# So we bake a hook script that the feature's postCreateCommand invokes.
HOOK_DIR=/usr/local/share/rocicorp-pnpm
HOOK="$HOOK_DIR/post-create.sh"
mkdir -p "$HOOK_DIR"

cat > "$HOOK" <<'EOS'
#!/usr/bin/env bash
set -euo pipefail
# Install the pnpm version pinned in the workspace's package.json (packageManager field).
corepack install 2>/dev/null || echo "pnpm feature: no packageManager field found; skipping 'corepack install'."
EOS

if [ "$REMOVE_NPM" = "true" ]; then
cat >> "$HOOK" <<'EOS'
# Enforce pnpm: drop npm/npx so they can't be used by mistake.
sudo rm -rf /usr/local/bin/npm /usr/local/bin/npx /usr/local/lib/node_modules/npm 2>/dev/null || true
EOS
fi

chmod +x "$HOOK"
echo "Wrote postCreate hook to $HOOK (removeNpm=$REMOVE_NPM)."
13 changes: 13 additions & 0 deletions test/pnpm/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail

source dev-container-features-test-lib

# corepack enable installs the pnpm shim onto PATH at build time.
check "pnpm shim on PATH" bash -c "command -v pnpm"
check "postCreate hook generated" bash -c "test -x /usr/local/share/rocicorp-pnpm/post-create.sh"
# removeNpm defaults to true, so the generated postCreate hook should drop npm/npx.
# (The removal itself runs at postCreate, after build-time installs.)
check "hook removes npm (removeNpm default true)" bash -c "grep -q 'rm -rf /usr/local/bin/npm' /usr/local/share/rocicorp-pnpm/post-create.sh"

reportResults