chore(agent-tools): bump the Claude Code pin 2.1.221 → 2.1.222 — re-verified, worktree posture confirmed live #291

Open
dominik.polakovics wants to merge 1 commit from afk/290 into main

Moves CLAUDE_CODE_VERSION + CLAUDE_CODE_SHA256_X64_MUSL together with the compat record, in one commit, as containers/agent-tools/versions.env and docs/ops.md § "Agent-tools images" require. No adapter code changed — the re-verification found no drift that needed one.

CLAUDE_CODE_VERSION=2.1.222
CLAUDE_CODE_SHA256_X64_MUSL=e0b0fb4005e1ac0ebcee136254c638722f1c49e171a23d0843c605d72aac9029

The blocking check passes

2.1.222 broadens worktree isolation to "file edits and Bash in every session type". Every lab session has a linked worktree as cwd, so this could have started refusing git commit/git push to the bare origin. It does not.

  • Driven live in a purpose-built lab-shaped fixture (reference bare clone + git worktree add, so .git is a file): an interactive claude --permission-mode auto --model haiku wrote a file, committed, and pushed. Verified in the bare origin (worktree HEAD == refs/heads/afk/probe), not from the model's narration. Dialog sequences byte-identical to 2.1.221; no new prompt, refusal, or isolation language. A subagent doing the same was verified on disk too.
  • The probe is not blind. The same --git-dir redirect is refused on 2.1.222 under claude --worktree ("…a worktree-isolated session's git operations must target its own worktree") and succeeds on 2.1.221 — the changelog's bug and fix, reproduced in both directions.
  • Mechanically: the guard keys on isolationRoot ?? agentWorktree, where isolationRoot resolves through Bg()?.worktreePath — a record only Claude Code's own worktree flows set (--worktree/-w, /fork, WorktreeCreate, Agent-tool isolation:"worktree"). It is never derived from cwd or from .git being a file, so lab's externally-created worktrees never arm it.

Verification actually run

Gate Result
Digest re-derived from downloaded bytes matches manifest.json
musl artifact (the image input) executed reports 2.1.222 (Claude Code) — via the loader the deployed image bundles at /opt/lab/lib/ld-musl-x86_64.so.1, the same proof Containerfile.claude's pre-patch sanity run makes
Live recipe suite, outgoing then incoming, diffed six tests, zero skips, identical verdict sequences on both
§9 hook contract, live A/B spool lands with pinned fields, exit-0 observational contract holds, PostToolUse deletes it, additive merge proven
§10 reverse-grep re-scrape (musl + glibc) 33/33 descriptions, 13/13 argHints verbatim — no drift
§1 CLI probes --help diff is one added line (import, unused by lab); 12/12 subcommand helps byte-identical; --permission-mode still accepts auto; --effort values unchanged; model catalog byte-identical
§2/§4/§4a/§5/§9/§11/§12 static markers no drift
go test ./... exit 0
golangci-lint run ./... 0 issues
gofmt -l clean

§9 needed its own probe because the live recipe suite never wires the dialog spool — that gap is now recorded in the doc so the next sweep doesn't assume coverage it doesn't have.

Corrections this pass makes

  • The issue's ultraplan premise is wrong. "Removed ultraplan feature" is not observable in the bundle — the row is still present and identical, gated by a remote-config flag, grep -c returning 220 on all three artifacts. So a registry row-count drop is not a valid extractor-freshness check. The embedded VERSION/GIT_SHA pair is, and it matches the manifest's commit. ultraplan was never pinned, so no commands.go edit either way.
  • §10's registry count is 106, not 105help sat behind a resync failure in the older extractor. Offered with its evidence, not asserted as proof. Same count on both versions, so the A/B answer is unaffected.
  • §10's feedback alias is a misattributionfeedback has no aliases; aliases:["share"] belongs to the separate, unpinned bug row. doctor carries an undocumented checkup. Both pre-existing and identical on 2.1.221.

Not done, and why — please read before merging

  • §3b (credential-refresh trigger) is NOT discharged. It is now recorded as structurally un-runnable from inside an instance, not merely deferred: masterConfigDir() there resolves to an injected consumer snapshot, so the forge-and-poke recipe would rotate the shared OAuth family from a snapshot and invalidate the master plus every sibling instance — exactly the failure the credential-authority seam exists to prevent. An operator must run it on the lab host. Unchanged by this bump.
  • The agent-tools image build + injection smoke test were not run locally — no container runtime in this environment. They are covered by the path-gated CI job on this PR, which is the gate that matters.
  • Not re-driven, so not claimed: the §12 pending-tool_use flush A/B; the §2 deep-link live capture; the permission_prompt Notification variant (only idle_prompt driven); and the changelog's PreToolUse auto-allow/background-task fix, which left no distinguishing string literal in the bundle and so is unsettled by static extraction.

Also worth knowing

The new SendMessage classifier gate is Ejs(e){return e==="auto"||e==="plan"&&GB()}auto is lab's spawn mode, so that dispatch path now goes through the classifier rather than an unconditional allow. Defence in depth; no lab-side change.

Closes #290

Moves `CLAUDE_CODE_VERSION` + `CLAUDE_CODE_SHA256_X64_MUSL` **together with the compat record**, in one commit, as `containers/agent-tools/versions.env` and `docs/ops.md § "Agent-tools images"` require. No adapter code changed — the re-verification found no drift that needed one. ``` CLAUDE_CODE_VERSION=2.1.222 CLAUDE_CODE_SHA256_X64_MUSL=e0b0fb4005e1ac0ebcee136254c638722f1c49e171a23d0843c605d72aac9029 ``` ## The blocking check passes 2.1.222 broadens worktree isolation to *"file edits and Bash in every session type"*. Every lab session has a linked worktree as cwd, so this could have started refusing `git commit`/`git push` to the bare origin. **It does not.** - Driven **live** in a purpose-built lab-shaped fixture (reference bare clone + `git worktree add`, so `.git` is a *file*): an interactive `claude --permission-mode auto --model haiku` wrote a file, committed, and pushed. Verified **in the bare origin** (worktree `HEAD` == `refs/heads/afk/probe`), not from the model's narration. Dialog sequences byte-identical to 2.1.221; no new prompt, refusal, or isolation language. A subagent doing the same was verified on disk too. - **The probe is not blind.** The same `--git-dir` redirect **is refused on 2.1.222** under `claude --worktree` (*"…a worktree-isolated session's git operations must target its own worktree"*) and **succeeds on 2.1.221** — the changelog's bug and fix, reproduced in both directions. - **Mechanically:** the guard keys on `isolationRoot ?? agentWorktree`, where `isolationRoot` resolves through `Bg()?.worktreePath` — a record only Claude Code's own worktree flows set (`--worktree`/`-w`, `/fork`, `WorktreeCreate`, Agent-tool `isolation:"worktree"`). It is never derived from cwd or from `.git` being a file, so lab's externally-created worktrees never arm it. ## Verification actually run | Gate | Result | |---|---| | Digest re-derived from downloaded bytes | matches `manifest.json` | | **musl artifact (the image input) executed** | reports `2.1.222 (Claude Code)` — via the loader the deployed image bundles at `/opt/lab/lib/ld-musl-x86_64.so.1`, the same proof `Containerfile.claude`'s pre-patch sanity run makes | | Live recipe suite, outgoing **then** incoming, diffed | six tests, **zero skips**, identical verdict sequences on both | | §9 hook contract, live A/B | spool lands with pinned fields, exit-0 observational contract holds, `PostToolUse` deletes it, **additive merge proven** | | §10 reverse-grep re-scrape (musl + glibc) | **33/33** descriptions, **13/13** argHints verbatim — no drift | | §1 CLI probes | `--help` diff is **one added line** (`import`, unused by lab); 12/12 subcommand helps byte-identical; `--permission-mode` still accepts `auto`; `--effort` values unchanged; model catalog byte-identical | | §2/§4/§4a/§5/§9/§11/§12 static markers | no drift | | `go test ./...` | exit 0 | | `golangci-lint run ./...` | 0 issues | | `gofmt -l` | clean | §9 needed its own probe because the live recipe suite never wires the dialog spool — that gap is now recorded in the doc so the next sweep doesn't assume coverage it doesn't have. ## Corrections this pass makes - **The issue's `ultraplan` premise is wrong.** *"Removed ultraplan feature"* is **not observable in the bundle** — the row is still present and identical, gated by a remote-config flag, `grep -c` returning 220 on all three artifacts. So a registry row-count drop is **not** a valid extractor-freshness check. The embedded `VERSION`/`GIT_SHA` pair is, and it matches the manifest's `commit`. `ultraplan` was never pinned, so no `commands.go` edit either way. - **§10's registry count is 106, not 105** — `help` sat behind a resync failure in the older extractor. Offered with its evidence, not asserted as proof. Same count on both versions, so the A/B answer is unaffected. - **§10's `feedback` alias is a misattribution** — `feedback` has no aliases; `aliases:["share"]` belongs to the separate, unpinned `bug` row. `doctor` carries an undocumented `checkup`. Both pre-existing and identical on 2.1.221. ## Not done, and why — please read before merging - **§3b (credential-refresh trigger) is NOT discharged.** It is now recorded as *structurally un-runnable from inside an instance*, not merely deferred: `masterConfigDir()` there resolves to an injected **consumer snapshot**, so the forge-and-poke recipe would rotate the shared OAuth family *from* a snapshot and invalidate the master plus every sibling instance — exactly the failure the credential-authority seam exists to prevent. **An operator must run it on the lab host.** Unchanged by this bump. - **The agent-tools image build + injection smoke test were not run locally** — no container runtime in this environment. They are covered by the path-gated CI job on this PR, which is the gate that matters. - **Not re-driven, so not claimed:** the §12 pending-`tool_use` flush A/B; the §2 deep-link live capture; the `permission_prompt` Notification variant (only `idle_prompt` driven); and the changelog's PreToolUse auto-allow/background-task fix, which left **no distinguishing string literal** in the bundle and so is unsettled by static extraction. ## Also worth knowing The new `SendMessage` classifier gate is `Ejs(e){return e==="auto"||e==="plan"&&GB()}` — **`auto` is lab's spawn mode**, so that dispatch path now goes through the classifier rather than an unconditional allow. Defence in depth; no lab-side change. Closes #290
chore(agent-tools): bump the Claude Code pin 2.1.221 → 2.1.222
All checks were successful
agent-tools / publish (pull_request) Has been skipped
ci / native (pull_request) Successful in 8m14s
agent-tools / smoke (pull_request) Successful in 24m29s
54560fca81
Move CLAUDE_CODE_VERSION and CLAUDE_CODE_SHA256_X64_MUSL together with the
compat record, in one commit, as versions.env and docs/ops.md § "Agent-tools
images" require. The re-verification is the work; the two-line versions.env
edit is the trivial part.

The digest is re-derived from the downloaded bytes, and this pass goes one
better than 2.1.221's: the linux-x64-musl artifact — the image input itself,
not a twin — was EXECUTED here through the musl loader the deployed
agent-tools image already bundles at /opt/lab/lib/ld-musl-x86_64.so.1, the
same proof Containerfile.claude's pre-patch sanity run makes. It reports
2.1.222 (Claude Code).

The blocking check passes. 2.1.222 broadens worktree isolation to "file edits
and Bash in every session type", which could have restricted lab sessions
(every one has a linked worktree as cwd) from pushing to the bare origin. It
does not: driven live in a lab-shaped fixture, commit + push under
--permission-mode auto behave byte-identically to 2.1.221, verified in the
bare origin rather than from the model's narration. The probe is not blind —
the same --git-dir redirect IS refused on 2.1.222 under `claude --worktree`
and allowed on 2.1.221. Mechanically, the guard keys on Bg()?.worktreePath,
a record only Claude Code's own worktree flows set; it is never derived from
cwd or from .git being a file, so lab's externally-created worktrees never
arm it.

Live gate, per the doc's own procedure — outgoing binary first, then
incoming, then diff: six tests, zero skips, identical verdict sequences on
both. §9 needed its own probe (the recipe suite never wires the dialog
spool): the spool lands with the pinned fields on both versions, the
observational exit-0 contract holds, PostToolUse deletes it, and additive
merge is proven by a repo-scoped hook and lab's --settings hook both firing
for the same tool_use_id.

§10 re-scraped by the reverse-grep method from the sha256-verified musl
artifact and its glibc twin: 33/33 pinned descriptions and 13/13 argHints
verbatim, no drift, no commands.go edit needed. Two corrections to §10's own
record, both pre-existing and identical on 2.1.221: the registry is 106 rows
rather than 105 (`help` sat behind a resync failure in the older extractor),
and `feedback` carries no aliases — `aliases:["share"]` belongs to the
separate, unpinned `bug` row. Also correcting the bump issue's premise: the
"Removed ultraplan feature" note is NOT observable in the bundle, so a
row-count drop is not a valid extractor-freshness check; the embedded
VERSION/GIT_SHA pair is, and it matches the manifest commit.

§3b is not discharged, and is now recorded as structurally un-runnable from
inside an instance rather than merely deferred: masterConfigDir() resolves to
an injected consumer snapshot there, so the forge-and-poke recipe would
rotate the shared OAuth family from a snapshot and log out the master and
every sibling. It must be run on the lab host by an operator.

Closes #290

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

[autoland] verdict: pass
All checks were successful
agent-tools / publish (pull_request) Has been skipped
ci / native (pull_request) Successful in 8m14s
Required
Details
agent-tools / smoke (pull_request) Successful in 24m29s
This pull request is blocked because it's outdated.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin afk/290:afk/290
git switch afk/290
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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
Cloonar/coding-lab!291
No description provided.