Skip to content

Pin Claude Code CLI to 2.1.263 - #1797

Merged
ppXD merged 1 commit into
mainfrom
chore/pin-claude-code-cli-2.1.263
Sep 6, 2026
Merged

ppXD merged 1 commit into
mainfrom
chore/pin-claude-code-cli-2.1.263

Conversation

@ppXD

@ppXD ppXD commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Bump the pinned Claude Code CLI from 2.1.193 to 2.1.263 (npm latest) across the worker image ARG, ClaudeCodeHarness.DefaultVersion, and the four exact-pinned installs in real-model.yml. The five deliberately floating @~2.1.0 installs are left floating — they already resolve to 2.1.263, which is why the exact pins were the ones certifying an older CLI than the lanes actually exercising the product.
  • Move the agent-cli stage to node:22. claude-code 2.1.263 raises engines.node to >=22 (2.1.193 wanted >=18); node:20 still installs it, but only because EBADENGINE is a warning. The shipped claude is a self-contained binary that never runs under the image's node, so this is about keeping an engine-strict npm — or a future install script using node-22 syntax — from turning that warning into a failed image build. codex 0.142.2 declares >=16 and is unaffected.
  • Add a pin test asserting every exact @anthropic-ai/claude-code@<version> literal in real-model.yml equals CLAUDE_CODE_VERSION. Nothing asserted this before, so a workflow left behind on a bump would have silently gated on a CLI the worker never ships. The floating installs are excluded by the version pattern.

Note: npm's stable dist-tag is 2.1.236, behind latest. This PR targets latest as specified; say the word if stable is the better pin for the worker image.

Test plan

  • Flag surface — captured --help from both versions and diffed. Every token the harness emits still exists: --print, --output-format stream-json, --verbose, --resume, --append-system-prompt, --setting-sources user, --settings, --model, --allowedTools/--allowed-tools, --permission-mode, --mcp-config, --strict-mcp-config. Value choices for --output-format (text|json|stream-json) and --setting-sources (user|project|local) are unchanged. The one rename that touches this flag — --permission-mode listing "manual" where 2.1.193 listed "default" — misses us: the harness only ever emits plan or bypassPermissions, and both are still in the choice list. Proven at runtime, not just in help text: running the full harness argv against the real 2.1.263 binary reached auth (i.e. past argument validation) and the system/init event echoed back "permissionMode":"plan" and "permissionMode":"bypassPermissions" respectively.
  • Changelog — read anthropics/claude-code CHANGELOG across the 2.1.194–2.1.263 window. Nothing breaking for our surfaces. The entries that touch us and why they are safe: "Changed the "default" permission mode to "Manual" across the CLI... --permission-mode manual and \"defaultMode\": \"manual\" are accepted alongside default" (a display rename; default still accepted, and we emit neither). "Changed project-level .claude/settings.json env to no longer set CLAUDE_CONFIG_DIR..." — we set CLAUDE_CONFIG_DIR as a real process env var via ConfigHomeEnvVars, never through project settings, and we pass --setting-sources user whenever we write settings, so project layers do not load. "Fixed hooks silently treating a stdout {…} object that isn't valid JSON as plain text; it's now reported as a hook error" — our Stop hook writes its block reason to stderr and exits 2, and drains stdin outright, so it never presents stdout JSON. "SIGTERM in print/SDK mode no longer records an interrupted turn or synthetic tool denials before exiting... still exits with code 143" — exit code preserved; a timed-out run may simply carry fewer trailing events. Two fixes land in our favour: blocking Stop hooks no longer lose the turn's reasoning, and session transcripts are no longer overwritten when a directory change relocates a session onto an existing same-ID transcript.
  • Transcript-path encoder — the highest-risk surface, proven unchanged three ways. (1) Extracted the encoder from the 2.1.263 binary: RA(e){let n=k(e);if(n.length<=az)return n;return \${n.slice(0,az)}-${Te(e)}`}withk=replace(/[^a-zA-Z0-9]/g,"-"), az=200, Te=Math.abs(gz(e)).toString(36), gz=(e<<5)-e+charCodeAt(r)|0— byte-identical in algorithm to 2.1.193'sab/Byu/hRe/pXe; only minified symbol names moved. defaultPath()still builds the projects segment asRA(cwd). (2) Ran the extracted functions over the existing ground-truth corpus: every pinned pair reproduces exactly, including the truncation branch's -e2qqelhash suffix at 207 chars. (3) Live: ran the real 2.1.263 binary from a cwd containing a space,_, .and non-ASCII and it created exactly-private-tmp-cs-enc-my-project-v2----x, which ClaudeTranscriptPath.EncodeCwd` reproduces. That pair is now pinned as regression data — it closes a corpus hole (no prior pair covered a space or non-ASCII).
  • Real-CLI MCP smoke — CODESPACE_RUN_REAL_CLI_MCP_SMOKE=1 CODESPACE_CLAUDE_CODE_PATH=<2.1.263> dotnet test --filter On_demand_the_real_claude_cli → 1 passed, 5s (11s wall). Not sub-second, so it genuinely launched the binary: the runner wrote the declaration into the per-run config home and the MCP handshake was observed.
  • Worker image — docker build --target agent-cli on the real Dockerfile.worker succeeds with no EBADENGINE. Separately replicated the load-bearing runtime smoke (the claude.exe symlink + codex --version && claude --version at lines 113–115) on both node:20 and node:22; node:22 prints codex-cli 0.142.2 and 2.1.263 (Claude Code) cleanly. Also confirmed claude.exe is a self-contained ELF that runs with no node on PATH.
  • Unit tests — new pin test verified red-green: it fails with "an exact '@anthropic-ai/claude-code@2.1.193' install in .github/workflows/real-model.yml lags CLAUDE_CODE_VERSION" when the Dockerfile is bumped and the workflow is not. Full CodeSpace.UnitTests green: 9199 passed, 0 failed (HarnessVersionPinTests, ClaudeTranscriptPathTests, AgentNativeRecordPumpTests, ClaudeCodeHarnessTests: 158 passed).
  • stream-json tool_use/tool_result capture — NOT DONE. A scratch-installed CLI does not share the local login (a trivial prompt returned Not logged in, total_cost_usd: 0, zero tokens), and auth was left untouched. From the auth-failing run the envelope surfaces the harness parses were still confirmed on 2.1.263: system/init with a canonical UUID session_id, assistant with message.content[] text blocks and message.model, and the terminal result with is_error, result, session_id, usage and total_cost_usd. The tool_use / tool_result / thinking block shapes were not exercised; the changelog records no change to them, and the real-model lane covers them.
  • Real-model lane on this branch

The 2.1.193 pin predates ~70 CLI releases and every flag the harness
emits was last verified against it. The floating `@~2.1.0` lanes have
been resolving to 2.1.263 for some time, so the exact-pinned lanes were
certifying an older CLI than the ones actually exercising the product.

Verified against the real 2.1.263 binary before bumping: every argv
token and value the harness emits still parses (`--permission-mode`
echoes back both `plan` and `bypassPermissions` in the init event; the
`default` -> `manual` rename in that flag's choice list does not touch
us, and upstream still accepts `default` anyway); the transcript-path
encoder is byte-identical, re-extracted from the binary and confirmed
live against a directory the CLI itself created; and the real-CLI MCP
smoke passes on the new binary.

Also moves the agent-cli stage to node:22, because claude-code 2.1.263
raises `engines.node` to >=22 (2.1.193 wanted >=18). node:20 still
installs it, but only because EBADENGINE is a warning rather than an
error - the shipped `claude` is a self-contained binary that never runs
under the image's node. Tracking the declared floor keeps an
`engine-strict` npm, or a future install script using node-22 syntax,
from turning that warning into a failed image build. codex 0.142.2
declares >=16, so it is unaffected.

Adds a pin test asserting real-model.yml's exact-pinned installs equal
CLAUDE_CODE_VERSION, so a workflow left behind on a future bump fails at
test time instead of silently gating on a CLI the worker never ships.
@ppXD
ppXD merged commit 2ead55a into main Sep 6, 2026
25 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant