From 0b4afb5cb1c09dc5c078a82492d0ed327ce5974b Mon Sep 17 00:00:00 2001 From: mariuspruvot <162115590+mariuspruvot@users.noreply.github.com> Date: Sat, 1 Aug 2026 20:29:34 +0200 Subject: [PATCH] chore(infra): pin the Claude Code CLI and nginx, move the runner off EOL node `npm install -g @anthropic-ai/claude-code` was unpinned, on the image whose output the entire SSE pipeline parses. The five stream-json event types the backend depends on are that CLI's contract, not ours, so an upstream format change would have broken production on the next image rebuild -- no code change, no diff to review, nothing in CI to catch it. Pinned to 2.1.220 via an ARG, with the bump procedure written down next to it. node:20-slim reached end of life in April 2026, on the one image that executes untrusted PR content, while the web build was already on 22. Now node:22-slim: verified the runner still builds and that the entrypoint's awk substitution keeps neutralising the injection payload on the new base. `nginx:alpine` had no version component, so a new nginx major would land in production on a rebuild with nothing to review. Pinned to 1.29-alpine. `gh` is left unpinned on purpose: apt pins break as soon as the old version leaves the repository, and it is installed from GitHub's own signed keyring. Verified inside the built image: node v22.23.2, claude 2.1.220, gh 2.97.0. --- CLAUDE.md | 3 +++ infra/docker/Dockerfile.web | 4 +++- infra/docker/claude-runner/Dockerfile | 13 +++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ca22b2f..b97831e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,6 +108,9 @@ Key additions for production: `ENVIRONMENT=production`, `ADMIN_PASSWORD`, `CORS_ ## Gotchas +- **Bumping the Claude Code CLI is a deliberate act**: `CLAUDE_CODE_VERSION` is pinned in `infra/docker/claude-runner/Dockerfile`. The SSE pipeline parses that CLI's stream-json output, so an upstream format change breaks production at image-rebuild time with no code change and no CI signal. To bump: raise the ARG, rebuild, run a session end to end, and check the five event types still arrive. + + - **CI coverage threshold**: `--cov-fail-under=70` (current coverage ~75%). Raising to 80% requires covering `admin/views.py`, `main.py` lifespan, and more router branches. - **Lost stats tests**: `tests/modules/identity/test_stats.py` (176 lines covering the user stats endpoint) was dropped in the #42 squash; it survives on local branch `feat/dashboard-activity-chart-impl` (62bf88e) — cherry-pick and adapt to boost coverage. - **Entrypoint parallel I/O**: clone, metadata (`gh pr view --json`), and diff (`gh pr diff`) run as background jobs with `wait`. The `-R` flag lets `gh` hit the API without a local `.git` dir. `set -e` does NOT propagate from background jobs — each `wait $pid` needs explicit `|| exit 1`. The claude-runner image includes `jq` for parsing the combined metadata JSON. diff --git a/infra/docker/Dockerfile.web b/infra/docker/Dockerfile.web index f8dd53e..4e936cc 100644 --- a/infra/docker/Dockerfile.web +++ b/infra/docker/Dockerfile.web @@ -19,7 +19,9 @@ COPY . . RUN npm run build # Production stage -FROM nginx:alpine AS production +# Pinned to a minor: an unqualified `nginx:alpine` would put a new nginx +# major into production on the next rebuild with no diff to review. +FROM nginx:1.29-alpine AS production COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf # NOTE: nginx.conf must be in the build context (apps/web/). diff --git a/infra/docker/claude-runner/Dockerfile b/infra/docker/claude-runner/Dockerfile index c5f2c2f..ec0b8d4 100644 --- a/infra/docker/claude-runner/Dockerfile +++ b/infra/docker/claude-runner/Dockerfile @@ -1,4 +1,13 @@ -FROM node:20-slim +# node 20 reached end of life in April 2026, on the one image that executes +# untrusted PR content. The web build is already on 22. +FROM node:22-slim + +# Pinned deliberately. The whole SSE pipeline parses this CLI's stream-json +# output -- the five event types in CLAUDE.md are its contract, not ours -- so +# an upstream format change would break production on the next image rebuild, +# with no code change and nothing in CI to catch it. Bump this on purpose, +# then re-run a session end to end. +ARG CLAUDE_CODE_VERSION=2.1.220 RUN apt-get update && apt-get install -y --no-install-recommends \ git \ @@ -11,7 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ | tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \ apt-get update && \ apt-get install -y --no-install-recommends gh && \ - npm install -g @anthropic-ai/claude-code && \ + npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} && \ apt-get clean && rm -rf /var/lib/apt/lists/* RUN useradd -m -s /bin/bash runner