Keeping developer CLIs fresh without breaking the build

Unifying developer CLI lifecycle management in the devcontainer, auto-updating on startup without mutating reviewed build pins, and handling package manager regressions.

The devcontainer’s multi-agent setup, established when Codex and Antigravity joined Claude Code, created a practical tension between container immutability and tool freshness. Pinned versions guarantee that every developer and agent works in an identical environment, but agent CLI binaries update rapidly with bug fixes and API adaptations. Resolving this meant unifying CLI lifecycle management: letting tools update on container startup while preserving reviewed rebuild pins in the manifest.

One manifest for every developer CLI#

Before this work, Claude Code was installed via a devcontainer feature, while Codex CLI and Antigravity CLI were managed through custom shell scripts. Bringing all three tools into a single manifest (developer-clis.json) provided a unified source of truth for version pins, binary paths, health checks, and firewall allowlist entries. The value of explicit version pinning became clear when an upstream release of pnpm (11.13.0) broke container initialization, requiring a quick pin to 11.13.1 in package.json to keep devcontainer builds reliable.

Non-mutating startup updates#

The new design separates day-to-day startup from full container rebuilds. On startup, post-start.sh checks for updates and refreshes the installed CLI binaries in user-writable paths without altering the pinned versions in developer-clis.json. If a network lookup fails or the firewall blocks an update endpoint, startup logs a warning and continues with the installed binary rather than halting container initialization.

# Claude Code is the primary developer CLI in this repo. Keep the feature-
# installed binary on its latest release without blocking startup if the
# provider is temporarily unreachable.
claude update \
    || echo "WARN: unable to auto-update Claude Code; continuing with the installed version" >&2

bash .devcontainer/check-developer-cli-updates.sh \
    || echo "WARN: unable to check developer CLI updates; the pinned environment is unchanged" >&2

Enforcing lifecycle invariants#

A comprehensive validation script (validate-developer-clis.sh) enforces these invariants across static verification and runtime health checks. The script verifies that installed CLI versions match manifest declarations, checks that required binaries sit on the execution path, and confirms that offline fallback behaviors execute cleanly. Updating the manifest itself remains an explicit script invocation (update-developer-cli.sh), ensuring that version changes land in git commits through reviewable pull requests.