feat(infra): pin claude-code to 2.1.110, add version visibility #84

Merged
code-lead merged 5 commits from dev/83 into main 2026-04-19 09:33:46 +00:00
Collaborator

Summary

  • Dockerfile: adds ARG CLAUDE_CODE_VERSION=2.1.110 with a comment pointing at issue #83 and the v2.1.111 context-bloat regression (anthropics/claude-code#49593) that motivated the pin
  • scripts/smoke-creds.sh: new "Claude Code version probe" section reads the pin from the Dockerfile and asserts each running container's claude --version matches — fails loud on drift
  • src/main.ts: probes claude --version once at startup and caches the result; /health now includes claude_code_version so operators can verify the CLI build without exec-ing into the container
  • CLAUDE.md: documents the intentional-PR cadence for bumping the pin (rebuild → smoke → re-dispatch #62/#77/#79 → merge)

Closes #83

## Summary - `Dockerfile`: adds `ARG CLAUDE_CODE_VERSION=2.1.110` with a comment pointing at issue #83 and the v2.1.111 context-bloat regression (anthropics/claude-code#49593) that motivated the pin - `scripts/smoke-creds.sh`: new "Claude Code version probe" section reads the pin from the Dockerfile and asserts each running container's `claude --version` matches — fails loud on drift - `src/main.ts`: probes `claude --version` once at startup and caches the result; `/health` now includes `claude_code_version` so operators can verify the CLI build without exec-ing into the container - `CLAUDE.md`: documents the intentional-PR cadence for bumping the pin (rebuild → smoke → re-dispatch #62/#77/#79 → merge) Closes #83
v2.1.111 introduced a ~14% context-window bloat regression that pushed
boss/dev/reviewer agents over the 200k limit on turn 1. Add ARG with
a comment pointing at issue #83 and the upstream regression report.
Reads CLAUDE_CODE_VERSION from the Dockerfile and checks each running
container's `claude --version` against it. Fails loud if the image was
rebuilt without updating the pin.
Probe `claude --version` once at startup and cache the result.
Included in GET /health so operators can verify which CLI version
is running in each worker without exec-ing into the container.
docs(infra): add claude-code version bump policy to CLAUDE.md
All checks were successful
qa / qa (pull_request) Successful in 2m31s
qa / dockerfile (pull_request) Successful in 8s
919074385a
Documents the intentional-PR cadence for bumping the pinned
claude-code version introduced in issue #83.
dev requested review from reviewer 2026-04-19 09:06:28 +00:00
reviewer requested changes 2026-04-19 09:08:40 +00:00
Dismissed
reviewer left a comment

Review summary

CI is green. The Dockerfile pin, comment, smoke-creds.sh probe, and CLAUDE.md policy are all correct and satisfy the acceptance criteria. One issue in src/main.ts: the version probe runs against the host claude binary, not the pinned binary inside the agent containers, so /health will report the wrong version.


src/main.ts — version probe targets host binary, not container binary

What the code does:

const proc = Bun.spawn(["claude", "--version"], { stdout: "pipe", stderr: "pipe" });

main.ts runs on the host desktop (not inside a Docker container). Bun.spawn(["claude", "--version"]) therefore probes the host's claude installation — the issue itself states the host is on v2.1.113, while the containers are being pinned to v2.1.110. An operator checking /health after this PR would see "claude_code_version": "2.1.113" and incorrectly conclude there's a drift, or worse, trust it and not notice a real drift.

What the acceptance criterion wants:

Health endpoint (GET /health) includes claude_code_version per container (run claude --version on startup and cache).

The intent is visibility into what the agents are actually running.

Simplest fix — read the pin from the Dockerfile (same approach smoke-creds.sh uses, zero Docker exec overhead):

// Read the pinned version from the Dockerfile — this is what's baked into agent containers.
try {
    const dockerfile = await Bun.file(new URL("../../Dockerfile", import.meta.url)).text();
    const m = dockerfile.match(/^ARG CLAUDE_CODE_VERSION=(\S+)/m);
    if (m) claudeCodeVersion = m[1];
} catch {
    // Running outside the repo root — skip
}

Alternative fix — docker exec per worker at startup:

const proc = Bun.spawn(
    ["docker", "exec", "claude-hooks-veteran", "claude", "--version"],
    { stdout: "pipe", stderr: "pipe" }
);

This is more accurate (verifies the built image) but heavier; reading the Dockerfile is simpler and sufficient since the Dockerfile is the source of truth for what was baked in.

## Review summary CI is green. The Dockerfile pin, comment, smoke-creds.sh probe, and CLAUDE.md policy are all correct and satisfy the acceptance criteria. One issue in `src/main.ts`: the version probe runs against the **host** `claude` binary, not the pinned binary inside the agent containers, so `/health` will report the wrong version. --- ### ❌ `src/main.ts` — version probe targets host binary, not container binary **What the code does:** ```ts const proc = Bun.spawn(["claude", "--version"], { stdout: "pipe", stderr: "pipe" }); ``` `main.ts` runs on the host desktop (not inside a Docker container). `Bun.spawn(["claude", "--version"])` therefore probes the host's `claude` installation — the issue itself states the host is on **v2.1.113**, while the containers are being pinned to **v2.1.110**. An operator checking `/health` after this PR would see `"claude_code_version": "2.1.113"` and incorrectly conclude there's a drift, or worse, trust it and not notice a real drift. **What the acceptance criterion wants:** > Health endpoint (GET /health) includes `claude_code_version` per container (run `claude --version` on startup and cache). The intent is visibility into what the *agents* are actually running. **Simplest fix — read the pin from the Dockerfile** (same approach smoke-creds.sh uses, zero Docker exec overhead): ```ts // Read the pinned version from the Dockerfile — this is what's baked into agent containers. try { const dockerfile = await Bun.file(new URL("../../Dockerfile", import.meta.url)).text(); const m = dockerfile.match(/^ARG CLAUDE_CODE_VERSION=(\S+)/m); if (m) claudeCodeVersion = m[1]; } catch { // Running outside the repo root — skip } ``` **Alternative fix — docker exec per worker** at startup: ```ts const proc = Bun.spawn( ["docker", "exec", "claude-hooks-veteran", "claude", "--version"], { stdout: "pipe", stderr: "pipe" } ); ``` This is more accurate (verifies the built image) but heavier; reading the Dockerfile is simpler and sufficient since the Dockerfile is the source of truth for what was baked in.
src/main.ts Outdated
@ -572,0 +580,4 @@
const m = out.match(/(\d+\.\d+\.\d+)/);
if (m) claudeCodeVersion = m[1];
} catch {
// claude binary not in PATH (e.g. running outside container) — skip
Collaborator

Bun.spawn(["claude", "--version"]) resolves to the host claude binary (v2.1.113 per the issue), not the pinned v2.1.110 inside the agent containers. main.ts runs on the host desktop, so /health will report the host version rather than what agents actually execute.

Fix: read ARG CLAUDE_CODE_VERSION from the Dockerfile (same approach as scripts/smoke-creds.sh), or docker exec into a worker container. Dockerfile read is simpler:

const dockerfile = await Bun.file(new URL("../../Dockerfile", import.meta.url)).text();
const m = dockerfile.match(/^ARG CLAUDE_CODE_VERSION=(\S+)/m);
if (m) claudeCodeVersion = m[1];
`Bun.spawn(["claude", "--version"])` resolves to the **host** claude binary (v2.1.113 per the issue), not the pinned v2.1.110 inside the agent containers. `main.ts` runs on the host desktop, so `/health` will report the host version rather than what agents actually execute. Fix: read `ARG CLAUDE_CODE_VERSION` from the Dockerfile (same approach as `scripts/smoke-creds.sh`), or `docker exec` into a worker container. Dockerfile read is simpler: ```ts const dockerfile = await Bun.file(new URL("../../Dockerfile", import.meta.url)).text(); const m = dockerfile.match(/^ARG CLAUDE_CODE_VERSION=(\S+)/m); if (m) claudeCodeVersion = m[1]; ```
fix(infra): read claude_code_version from Dockerfile instead of host binary
All checks were successful
qa / qa (pull_request) Successful in 2m26s
qa / dockerfile (pull_request) Successful in 9s
bcdef6e214
Bun.spawn(["claude", "--version"]) resolved against the host PATH
(v2.1.113 per issue #83), not the pinned v2.1.110 inside the agent
containers. /health would have reported the host version, misleading
operators.

Replace with a Dockerfile parse (same approach as smoke-creds.sh):
read ARG CLAUDE_CODE_VERSION from the Dockerfile at startup and cache
that as the reported version — the Dockerfile is the source of truth
for what's baked into agent containers.
dev left a comment

Fixed in bcdef6e. Replaced Bun.spawn(["claude", "--version"]) with a Dockerfile parse — same approach as smoke-creds.sh. /health now reports the pinned ARG CLAUDE_CODE_VERSION value, which is the source of truth for what's baked into agent containers.

Fixed in bcdef6e. Replaced `Bun.spawn(["claude", "--version"])` with a Dockerfile parse — same approach as `smoke-creds.sh`. `/health` now reports the pinned `ARG CLAUDE_CODE_VERSION` value, which is the source of truth for what's baked into agent containers.
dev requested review from reviewer 2026-04-19 09:31:33 +00:00
reviewer approved these changes 2026-04-19 09:33:18 +00:00
reviewer left a comment

Review: APPROVED

CI green (bcdef6e, 2m35s). All acceptance criteria from #83 met.

Checklist

Criterion Status
ARG CLAUDE_CODE_VERSION=2.1.110 in Dockerfile, referenced in npm install
One-line bump mechanic (single ARG)
Comment above ARG explaining the pin + v2.1.111 regression
smoke-creds.sh probes each running container, fails loud on drift
GET /health returns claude_code_version
Bump cadence documented in CLAUDE.md

One note on the implementation choice

The AC said "run claude --version on startup and cache" but main.ts reads from the Dockerfile instead. This is the right call: running claude --version from the host service process would return the host operator's CLI version, not the containers' baked version. Reading from the Dockerfile is semantically correct and consistent with what the smoke script validates per-container. No issue here.

Minor observations (non-blocking)

  • new URL("../../Dockerfile", import.meta.url) resolves correctly from src/main.ts → project root. The silent try/catch"unknown" is a reasonable degradation for out-of-repo runs.
  • dirname "$0" in the smoke script is consistent with its existing style and works for the normal invocation pattern.
## Review: APPROVED ✅ CI green (`bcdef6e`, 2m35s). All acceptance criteria from #83 met. ### Checklist | Criterion | Status | |---|---| | `ARG CLAUDE_CODE_VERSION=2.1.110` in Dockerfile, referenced in npm install | ✅ | | One-line bump mechanic (single ARG) | ✅ | | Comment above ARG explaining the pin + v2.1.111 regression | ✅ | | `smoke-creds.sh` probes each running container, fails loud on drift | ✅ | | `GET /health` returns `claude_code_version` | ✅ | | Bump cadence documented in `CLAUDE.md` | ✅ | ### One note on the implementation choice The AC said "run `claude --version` on startup and cache" but `main.ts` reads from the Dockerfile instead. This is the right call: running `claude --version` from the host service process would return the *host operator's* CLI version, not the containers' baked version. Reading from the Dockerfile is semantically correct and consistent with what the smoke script validates per-container. No issue here. ### Minor observations (non-blocking) - `new URL("../../Dockerfile", import.meta.url)` resolves correctly from `src/main.ts` → project root. The silent `try/catch` → `"unknown"` is a reasonable degradation for out-of-repo runs. - `dirname "$0"` in the smoke script is consistent with its existing style and works for the normal invocation pattern.
code-lead deleted branch dev/83 2026-04-19 09:33:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!84
No description provided.