chore(agent-tools): bump the Claude Code pin 2.1.220 → 2.1.221 — Bash permission-check fix; compat re-verification after #235 [schedule: Daily agent update check] #273
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#273
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Move the agent-tools Claude Code pin from
2.1.220to2.1.221, re-verifying the compat record against the new binary first, exactly ascontainers/agent-tools/versions.env:11-16anddocs/ops.md § "Agent-tools images"mandate:The re-verification is the work. The two-line
versions.envedit is the trivial part. If re-verification finds drift the record cannot absorb, the correct outcome is to report the drift on this issue and land only the compat-record updates — not to force the bump through.Sequencing — read #235 first
#235 is a hard prerequisite and should land first (or in the same run). It covers the live tmux recipe suite that the 2.1.220 pin shipped without, plus two stale assertions in
internal/compat/live_recipes_test.gothat fail before the suite can tell you anything. Running this issue's re-verification on top of a live suite that has never been green on 2.1.220 gives you no baseline: you would not be able to tell 2.1.221 drift from the pre-existing #56 assertion bug.Concretely: do #235's Work item 1 (fix the two assertions) and Work item 2 (green live run) first, then re-run the same suite against 2.1.221 and diff the outcomes.
#235 contains a claim that is now false and should not be trusted when you read it: "2.1.220 is still the newest release as of 2026-07-31 … There is nothing newer to chase; do not open the pin again as part of this." 2.1.221 published 2026-08-03. That statement was accurate when written and is superseded by this issue.
Evidence — current state
containers/agent-tools/versions.env:20-21:internal/compat/compat.md:3pins Claude Code 2.1.220, statically verified 2026-07-28 (bundle extraction + in-container CLI probes), live recipes still owed — that debt is #235.Upstream, npm
@anthropic-ai/claude-codedist-tags read on 2026-08-04:Note the channel shape: our current pin
2.1.220is now thestablechannel, and2.1.221islatest/next. The 2.1.220 bump (#235, commit52a9176) tooklatestoverstablefor a stated reason — 2.1.220 was the first release whose embedded catalog carriesaliases:{opus:{default:"claude-opus-5"}}. This bump needs its own reason; the security item below is it. If the implementing agent concludes the reason is not strong enough, saying so and closing this issue is a legitimate outcome — but say it explicitly rather than silently deferring.New pin values, from Anthropic's own per-version manifest (the source
versions.env:17-19names, and the exact artifactcontainers/agent-tools/build.sh:61-64fetches):Re-derive that digest yourself from the downloaded binary (
build.sh'sfetch_artifactverifies it, so a wrong value fails the build loudly rather than silently) — do not trust this issue as the sole source.Security — one item, and its reachability is conditional
2.1.221's changelog carries two permission-check fixes. Only one can reach lab, and whether it does depends on the operator's dev image:The PowerShell one does not reach us — lab spawns Linux containers only.
The zsh one reaches us conditionally, and this is the honest framing:
claudewith--permission-mode auto(internal/provider/claudecode/claudecode.go:419, argv documented at:392). Auto mode is exactly the mode whose classifier this bypass evades, and lab's whole model is unattended AFK runs — the permission check is the guard, and nobody is watching.grep -rn zshover this repo returns zero hits. The agent-tools image (containers/agent-tools/Containerfile.claude) isFROM scratchand ships theclaudebinary pluslabctl— no shell at all. The shell comes from the operator-supplied dev image (--container-image,internal/config/config.go:174; resolution atinternal/instance/container.go:58-80), which lab does not pin.Deliberately not counted as a reason, stated so nobody re-derives it:
This reads like it belongs to lab's credential-authority seam (issue #222,
internal/provider/claudecode/credauthority.go) — the single-refresher design that exists precisely because concurrent Claude Code processes refreshing one token family log each other out. It is the same class of bug, but a different token: the fix covers MCP connector and WIF OAuth tokens, and lab authenticates withauthMethod:"claude.ai"(compat.md§3, M3 live acceptance). The seam's.credentials.jsonfamily is untouched by this fix. Do not treat it as closing any part of #222.What changed that touches how this repo uses Claude Code
Every item below maps to a numbered section of
internal/compat/compat.md. Each is a hypothesis to verify against a real 2.1.221 binary, not a confirmed break — none of it was observed live during this investigation.§10 Builtin slash-command catalog (
internal/provider/claudecode/commands.go, pinned byTestCompat_BuiltinCommands_pinnedatinternal/compat/compat_test.go:877) — the highest-confidence drift./statusto show the session kind:interactive, or a background job that isattachedorunattended."/statusis a chat-safe row (commands.go:49) and §10 pins builtin descriptions verbatim ("descriptions and argHints verbatim",compat.md:1042). Our row reads: Re-extract it from the 2.1.221 bundle and confirm it still matches. A behavior change does not necessarily move the description string — but this is exactly what the verbatim pin is for.compat.md:1034, "bundle extraction (2.1.198, 2026-07-08)"), 33 rows. The 2.1.220 static pass verified the ten chat-safe rows are still present — it did not check for rows added since. 2.1.221's changelog references/fork("Changed sessions forked with/forkto create a new worktree of their own instead of working in the original session's checkout"), andgrep -rn fork internal/provider/claudecode/finds no such row. Do a fresh verbatim scrape at 2.1.221 and curate any new rows with the same rule §10 states (ChatSafe=trueiff the command executes inline and returns to the prompt)./forkin particular deserves an explicitChatSafedecision with a written reason: every lab instance already is a linked git worktree, so a command that creates a worktree of its own inside one is at best confusing and at worst fights lab's own worktree management — the §10 curation rule's second clause.§5/§8 Transcript + reply/interrupt recipes — lab's interrupt recipe is a single Escape, and the transcript JSONL is lab's entire chat surface.
ParseTranscripthas not seen.CLAUDE_CODE_RESUME_INTERRUPTED_TURN=0not disabling interrupted-turn auto-resume; falsy values are now honored." Lab does not set this variable (grep -rn CLAUDE_CODE_RESUME internal/→ no hits), so the default path is unchanged — but the note belongs in §8 next to the interrupt recipe, since interrupted-turn auto-resume is what lab's interrupt produces.--effortper run (claudecode.go:419); confirm the effort flag still drives what §1 says it does.§1 Spawn argv —
{claude} [--remote-control <session>] --permission-mode auto [--model M] [--effort E] [prompt]--permission-modedefault→manualrename noted on #235 is a 2.1.220-era item and lab pinsauto, which is unaffected — but fold it into §1 while you are there if #235 has not.--forward-subagent-text(2.1.219) remains unused.claude --versionreports2.1.221and that the embedded model catalog still resolvesopus[1m]→ Opus 5, the reason the 2.1.220 pin was taken.§3/§3a Auth + config dir — unchanged by 2.1.221 as far as the changelog shows. Re-run the CLI probes anyway (
claude auth status --json,CLAUDE_CONFIG_DIRhonored); they are cheap and they are what §3 pins.Not applicable, listed so the next sweep does not re-read them: the VSCode Focus view, Vim-mode yank/undo fixes, Windows startup and Bedrock/AWS-SSO fixes,
/pluginand marketplace changes, emoji shortcodes, Claude-in-Chrome tab handling, Gatewaymodelvalidation, Stats-panel token accounting, and the/ultrareviewerror-message changes. None of them touch a pinned coupling.Scope — files an implementing agent will touch
containers/agent-tools/versions.envCLAUDE_CODE_VERSION+CLAUDE_CODE_SHA256_X64_MUSL, moved atomically with the compat-record updateinternal/compat/compat.mdinternal/provider/claudecode/commands.go/statusdescription change)internal/compat/compat_test.goTestCompat_BuiltinCommands_pinnedneeds a new pinned string — and only with the bundle evidence quoted in the commit messageinternal/provider/claudecode/*.go+testdata/nix/module.nixneeds no edit —container.toolsImagesderives its default tag fromversions.envat eval time (nix/module.nix:197-213), andflake.nix:320-322prefix-greps the tag so a version bump never touches that check.Done means
containers/agent-tools/versions.envreadsCLAUDE_CODE_VERSION=2.1.221with the sha256 re-derived from the downloaded binary, andcontainers/agent-tools/build.sh claudebuilds the image with that digest verifying.containers/agent-tools/smoke-test.sh(driven the way.forgejo/workflows/agent-tools.ymldrives it).go test ./internal/compat/ ./internal/provider/claudecode/is green — in particularTestCompat_BuiltinCommands_pinned.LAB_COMPAT_LIVE=1 go test ./internal/compat/ -run Live -v. A skipped run is not a pass — the tests carry their own skip-gates and will quietly skip without tmux plus a logged-inclaudeon PATH; confirm from the-voutput that they ran./status's description either confirmed byte-identical or updated with the new bundle text quoted in the commit message; any builtin added since 2.1.198 either added to the table with a writtenChatSafereason, or explicitly recorded in §10 as deliberately omitted./forkhas an explicit, writtenChatSafedecision.internal/compat/compat.md:3reads 2.1.221 with per-section provenance re-dated to what was actually re-verified — static markers marked static, live recipes marked live, nothing claimed that was not observed.compat.mdnext to §1's--permission-mode auto, with the conditional-reachability reasoning (operator-supplied dev image) so it is not re-derived next sweep.go build -tags ui ./cmd/... && go test -tags ui ./...andgolangci-lint rungreen (make build/make test/make lint).Closes #273.Out of scope
0.146.0, still the newest stable as of 2026-08-04).Filed by the Daily agent update check schedule, 2026-08-04. Version data from
registry.npmjs.org/@anthropic-ai/claude-code; digest fromdownloads.claude.ai/claude-code-releases/2.1.221/manifest.json; changelog fromgithub.com/anthropics/claude-codeCHANGELOG.md. Nothing below §"What changed" was observed on a live 2.1.221 binary — every item is a hypothesis for the implementing agent to confirm or refute.PR #274 opened. Summary of what the re-verification actually found, including the two things worth a maintainer's attention.
The live gate is closed, with a real baseline. #235's work item 1 (the two stale
ReadChatassertions) and work item 2 (the green live run) are both in this PR, because the issue was right that 2.1.221 is unreadable without them. The suite ran against the outgoing 2.1.220 binary first, then 2.1.221: six tests, zero skips, identical verdicts on both. That is what makes "green" mean "no drift" here rather than "green for some other reason".Confirmed against a real binary (every hypothesis in the issue was checked, not assumed):
/status— byte-identical. The session-kind behavior changed; the description string did not. This was the highest-confidence predicted drift and it did not happen.--helpdiff 2.1.220 → 2.1.221 is one added line,--autocompact <auto|tokens>, which lab does not use.--permission-modestill takesauto;--effortvalues unchanged.opus→claude-opus-5catalog alias — intact, so the reason the 2.1.220 pin was taken survives.CLAUDE_CONFIG_DIRstill honored.Two findings you did not ask for, but should see:
Three pinned descriptions were already stale — on 2.1.220, not 2.1.221.
feedback,usage-creditsanddoctorno longer carry their 2.1.198 text. The 2.1.220 pass re-verified only the ten chat-safe rows, so it never looked at the curated-out ones. All three are ChatSafe=false, so nothing chat-facing ever served wrong text, andTestCompat_BuiltinCommands_pinnedcould not have caught it. Fixed with bundle evidence in the commit message./doctoris the interesting one: it is now a prompt-skill that runs as a model turn, carrying an operator-facingmenuDescriptionplus a long model-facingdescription. §10's stated reason for excluding it ("interactive diagnostics UI") was therefore factually wrong and would have argued for ChatSafe=true. Thefalseverdict survives, but on a different ground (it mutates operator-global and project config), and the reason is corrected in the record.A method bug that would have hidden this. A forward regex over
description:"…"reports a renamed key as an absent row — which is exactly how/doctorwas missed. The check that settles drift is the reverse direction: take each pinned string and grep the raw binary. §10 now says so, along with the fact that the documentedtr -c '[:print:]'dump silently drops strings inside long printable runs./forkgot the explicit written decision you asked for: ChatSafe=false. Worth noting it is genuinely enabled and visible in every lab session (its gate needsCLAUDE_CODE_COORDINATOR_MODE, which lab does not set), so the verdict is load-bearing rather than moot. Three grounds: transcript identity, worktree leakage, unsupervised background session./subtaskinherits it.The catalog gap is now measured rather than vague: 33 pinned rows against a 105-name registry. That 72-row delta is pre-existing — byte-identical at 2.1.220 — so it accumulated between 2.1.198 and 2.1.220. It is recorded in §10 as deliberately omitted with the add-candidates named (
helpis the strongest). I did not widen the served table: that changes what every operator sees in the composer, which reads like a product decision wanting its own issue rather than something to slip into a pin bump. Say the word if you would rather it went in here.One gap, stated plainly. The container build and injection smoke test did not run locally — this host has no container runtime and no root, so
build.shandsmoke-test.shcould not execute. Mitigations: the digest verifies underbuild.sh's exactsha256sum -ccheck, the artifact was executed through the loader the image bundles, and the PR is path-gated onto theagent-toolsCI workflow (versions.envis undercontainers/**), whosesmokejob builds both images and runs the injection test againstdebian:stable-slimandalpine. That CI leg is the authority on the image — please confirm it is green before merging.Also not re-driven, and therefore claimed nowhere in the record: the §12 pending-
tool_useflush A/B, the §2 deep-link registry (an ad-hoc probe failed identically on both versions — a broken probe, not drift), and the §3b by-hand credential-refresh recipe.