Skip to content

Run the review worker on a retained, hash-checked copy of the latest Claude Code (2.1.302) - #1127

Merged
seathatflowsinourveins merged 8 commits into
mainfrom
claude/cc-native-practice-retained-2299-pin-20261015
Oct 17, 2026
Merged

seathatflowsinourveins merged 8 commits into
mainfrom
claude/cc-native-practice-retained-2299-pin-20261015

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Scope

  • What this PR changes: the Claude review worker runs a retained, hash-checked copy of Claude Code (2.1.302 at the head) outside ~/.local/share/claude/versions/. srt moves to 0.0.80 with allowNestedUserNamespaces, and the keyless boundary check now also requires socket(AF_VSOCK) to be refused (fold round). Every path setting must be absolute, and a sha256 pin is required. Its preflight refuses a client binary whose sha256 is not the pinned one. A CLAUDE_BIN override naming another path needs its own CLAUDE_BIN_SHA256, or the worker stops at its settings (exit 2); the run rechecks the pin before its first debit.
  • Base commit: a339425ca56ec3fb87dff0fa8ad5dffc83354168
  • Lane: lane:foundation
  • Owned paths touched: tools/claude-review-worker/worker.py, tools/claude-review-worker/README.md, tests/test_claude_review_worker.py, manifests/evidence.json

SOTA sources

Evidence-class table

Claim Evidence class Command / receipt
The retained copy claude-retained/2.1.302/claude, versions/2.1.302 and the signed 2.1.302 manifest's linux-x64 checksum are the same digest, 60d7eb7f…29a5 (the binary prints 2.1.302 (Claude Code)) native_proven gpg --verify, sha256sum on this host, 2026-10-17T07:13:57Z
The rollback copy claude-retained/2.1.300/claude matches the signed 2.1.300 manifest, 674deba0…5453 native_proven gpg --verify, sha256sum on this host, 2026-10-17
2.1.300 and 2.1.302 have byte-identical --help texts, and the exact flag list gives the same init record with no key native_proven function check below, 2026-10-17
boundary_self_check with the vsock line (srt → bwrap → probe, no key): 0.0.79 fails on vsock alone, 0.0.80 without the opt-out fails at the inner bwrap, and 0.0.80 with it passes with vsock refused EPERM native_proven scratch installs of both registry tarballs, 2026-10-17T12:08:35Z to 12:08:38Z (fold round)
The inner bwrap --unshare-user and the key path work through srt 0.0.80 with the opt-out local_integration RealBoundaryChainTest with a fake key and a stand-in client, rc 0
probes.py offline on 2.1.302 and 2.1.300 through the srt 0.0.80 scratch prefix: 15 PASS each, the same O5 init native_proven scratch HOME, no key store (O6 FAIL by design), receipts 20261017T121205Z and 20261017T121214Z
The retained 2.1.299 copy, versions/2.1.299 and the signed manifest's linux-x64 checksum are the same digest, 2d894a4b…d077 native_proven gpg --verify, sha256sum on this host, 2026-10-15T00:46:54Z
The worker's own file_sha256 passes the real retained binary (0.24 s) local_integration in-process settings_from plus file_sha256 on this host
Preflight refuses a mismatched binary and accepts the pinned one; settings without a pin, or with a relative path, are refused synthetic test_preflight_refuses_a_client_binary_that_fails_its_sha256_check, test_settings_without_a_pin_are_refused, test_a_relative_client_binary_is_refused_even_with_its_digest
Defaults, the override rules and a malformed digest synthetic test_settings_read_the_environment

Function check, 2.1.300 → 2.1.302 (2026-10-17)

  • claude_flags() and review_settings(): the two --help texts are byte-identical (both sha256 223ec71c6e89368ce9d8d4b36118ec535a3af755728b7868fd60f69f6af7c64e, run from a scratch HOME and CLAUDE_CONFIG_DIR). All 17 flags the worker passes are present in both. No description changed.
  • The exact flag list: run with no key from a scratch config directory, it gives the same init record on both: tools Glob, Grep and Read, no MCP server, no plugin, no slash command or skill, apiKeySource none, permission mode dontAsk, and the same agent list. 2.1.302 adds two init fields, additional_directories (the --add-dir path) and original_cwd. The worker reads only tools, mcp_servers and plugins from the init record (analyze()).
  • probes.py offline ran keylessly in the fold round, against a scratch srt 0.0.80 prefix, with a scratch HOME that holds no key store. Both clients gave 15 PASS and the same O5 init; O6 fails by design. The run in the worker's own prefix stays Re-pin the review worker's Claude Code client to 2.1.299 #1107's step before enabling.

Function check, 2.1.299 → 2.1.300 (2026-10-15)

  • claude_flags(): every flag is present in both --help texts. The one change is --restricted's description, which drops REPL from the tools it removes; the worker passes --tools Read,Glob,Grep.
  • The exact flag list, run with no key from a scratch config directory, gives the same init record on both: tools Glob, Grep and Read, no MCP server, permission mode dontAsk.
  • probes.py offline cannot run on this host (srt 0.0.79 is not installed while the worker is disabled). It stays Re-pin the review worker's Claude Code client to 2.1.299 #1107's step before enabling.

Local commands run

$ python3 -m unittest tests.test_claude_review_worker   # this PR's tests against main's worker
Ran 96 tests, FAILED (failures=1, errors=3, skipped=1)
$ python3 -m unittest tests.test_claude_review_worker   # at 6b0535aa
Ran 96 tests, OK (skipped=1)
$ python3 -m unittest tests.<module>   # at 6b0535aa, one process per module, idle-class
rc 0 for all 5: test_claude_review_worker (96), test_runtime_worker_openhands_push_gate (127), test_local_pages_policy_grants (25), test_evidence_manifest (5), test_validate (86)

Decision record

No new decision. This carries out the command center's B2 ruling of 2026-10-15: protect the pinned roles before anything else, using the vendor-supported retained-binary option. The floor decision's 2026-10-14 addendum (in the floor-raise PR) records the cleanup exposure. The trading lane's equivalent change is its own private, dated runner copy.

Fix round 1 (795232e)

From GPT full1 (GPT-VERDICT-e6046ea38.md) and the command center's micro (pr1127-claude-micro-e6046ea3.md); the command center ruled at 2026-10-15T03:32Z that they land here, in one commit:

Finding Fix Fail-first test (failed at e6046ea, passes at 795232e)
Blocker 1 (P2) / micro F1: an override without a digest ran unchecked settings_from() raises ConfigError when CLAUDE_BIN names another path and CLAUDE_BIN_SHA256 is unset or empty; the default path keeps the built-in digest test_settings_read_the_environment (4 subtests: no ConfigError, a None digest); test_an_override_without_its_digest_never_builds_a_worker (crw.main(["select"]) built a worker; now exit 2)
P3-3 / micro F2: a hash-read OSError escaped tick()'s record path preflight() turns it into a "not ready" problem: a log line and a ticks.jsonl row test_an_unreadable_client_binary_is_a_recorded_refusal (a PermissionError traceback)
Micro F3: the run's re-hash was never compared with the pin run_keys() compares it before the first debit; a mismatch starts nothing and debits nothing (no_stream: not counted, the tick stops) test_a_binary_that_changes_after_preflight_starts_no_run (one launch and two ledger rows after the binary changed)
P3-4: no retained-copy rollback README, Operate: the 2.1.299 pair (path, and the linux-x64 checksum 2d894a4b…d077 with size 265342136 from the 2.1.299 release manifest, read 2026-10-15T03:37Z), the checks before enabling, and the undo; the test comment names 2.1.300 text

Own logic; no upstream mechanism changes. python3 -m unittest tests.test_claude_review_worker: 99 tests OK (1 skip). scripts/validate.py on the merge of 795232e with main 0f4ee07: rc 0. On the branch alone it reports the model-currency date checks that main's #1124 refreshed.

Fix round 2 (0fb64d8)

From GPT fix1 at 795232e (fix1-pr1127/GPT-VERDICT-795232e70.md), blocker 1 (P2). The local review at 795232e (review_output.json) found the same thing at worker.py:2054-2055. The command center released this commit at 2026-10-15T10:49Z.

Finding Fix Fail-first test (failed at 795232e, passes at 0fb64d8)
F3's comparison could use the previous binary's cached digest: binary_identity() cached by (path, size, mtime_ns), and a copy can keep both (python/cpython@cbc944f4 v3.13.16 Lib/shutil.py:348,385 copies nanosecond timestamps) run_keys() calls binary_identity(fresh=True), which hashes the file on every run. A read failure is a recorded refusal with nothing debited. test_a_replacement_keeping_size_and_mtime_is_caught_by_a_fresh_hash: the first review warms the cache, then same-size bytes go in at the same mtime_ns. It failed with (2, ['debit', 'settle', 'debit', 'settle']) != (1, ['debit', 'settle']).

python3 -m unittest tests.test_claude_review_worker: 100 tests OK (1 skip). On the branch alone, scripts/validate.py reports only the model-currency date checks that main has since refreshed. origin/main 4204520 passes (rc 0), and the landing rebase takes them.

Fix round 3, final (d7ca288)

The command center adjudicated the fix2 reads at 0fb64d8 (Astra fix2 delta fix2-pr1127/GPT-VERDICT-0fb64d83b.md; local review at 0fb64d8). This is the final round: below P1, findings close as Follow-ups.

Finding Fix Fail-first test (failed at 0fb64d8, passes at d7ca288)
(a) Both families' P2 (worker.py:2056-2057): a key switch launched without a new integrity check Before each further key, run_keys() hashes the file again. A mismatch or a read failure stops the run before the switch row, the debit and the launch. test_a_binary_replaced_before_the_next_key_starts_nothing_more: Lists differ: ['anthropic-api-4', 'anthropic-api-3'] != ['anthropic-api-4']
(e) Astra P3s: a persistent hash-read failure could escape while the refusal receipt was written, and the read-error correction lacked its own test The receipt reads the binary through readable_binary_identity(); the read-error refusal has its own test test_a_binary_that_cannot_be_read_at_the_run_is_a_recorded_refusal: PermissionError: [Errno 13] Permission denied

python3 -m unittest tests.test_claude_review_worker: 102 tests OK (1 skip). merge-tree against main 06f4ed2: rc 0.

Repin round (37dfd70)

Both runtime pins were stale, and the currency rule refreshes a stale pin at once. The command center dispatched this one commit on 2026-10-17.

Change Source Test (fails at d7ca288 with only the test file overlaid, passes at 37dfd70)
DEFAULT_CLAUDE_BIN → ~/.local/share/claude-retained/2.1.302/claude, DEFAULT_CLAUDE_BIN_SHA256 → 60d7eb7f…29a5; README env lines, --help note and rollback pair (now 2.1.300) signed 2.1.302 and 2.1.300 manifests (SOTA sources) test_settings_read_the_environment: '~/.local/share/claude-retained/2.1.300/claude' != '~/.local/share/claude-retained/2.1.302/claude'
srt held at 0.0.79, with a comment at the pin citing why sandbox-runtime@626a5967 README.md:715, commit 9a277e5 none: the pin does not move

CHANGELOG entries near the worker:

  • 2.1.302 line 34: claude -p with no permission mode starts in default on third-party providers. The worker passes --permission-mode dontAsk against the Anthropic API.
  • Line 35: fork subagents are on by default in claude -p. The worker's --tools Read,Glob,Grep offers no Agent tool, and the init record's tools did not change.
  • Line 10 (the Read tool refuses image sources over 128 MB) and 2.1.301 line 80 (Grep and Glob report a failed search as an error, not as no matches) touch the three allowed tools. Both only make them fail more visibly.

srt 0.0.80, what changes for the worker: the worker's chain is srt → credential_run.py → bwrap --unshare-user … → claude (SandboxLauncher.command). Since 0.0.80, by default srt's seccomp helper refuses the user-namespace flag to unshare and clone and sets user.max_user_namespaces to 0 (commit 9a277e5). The inner bwrap then stops. Measured with the worker's own boundary_self_check, keyless, on scratch installs of both tarballs:

  • 0.0.79: pass (lo only, API through srt, the rest refused);
  • 0.0.80: network boundary check exit 1: bwrap: No permissions to create a new namespace;
  • 0.0.80 with allowNestedUserNamespaces: true added to srt.json (measurement only, not committed): pass.

Adopting that opt-out trades away srt's write denies against the command. The inner bwrap's own read-only root stays. That trade is the command center's decision; its verdict at 37dfd70 adopted the opt-out with srt 0.0.80, and the fold round carries it out (0.0.79 had no namespace limit at any layer, so nothing is given up against it). The rest of the range checks out: the settings keys the worker writes are unchanged, domain-pattern.ts only takes over the validator that sandbox-config.ts used to hold, and the TMPDIR socket names are still claude-http-<id>.sock and claude-socks-<id>.sock (src/sandbox/linux-sandbox-utils.ts:1513,1516 at v0.0.80).

Commands at 37dfd70:

$ python3 -m unittest tests.test_claude_review_worker              # 102 tests OK (skipped=1), rc 0
$ (fail-first) the same at d7ca288c with only the test file overlaid  # FAILED (failures=1), rc 1
$ python3 scripts/evidence_manifest.py --check                       # passed, rc 0
$ venv-ci python scripts/validate.py --registration-base a339425c --registration-before d7ca288c   # rc 1: only the 9 inherited model-currency date checks
$ venv-ci python scripts/validate.py --registration-base 93aabf30    # on the merge of 37dfd70a with origin/main 93aabf30: passed, rc 0
$ git merge-tree --write-tree origin/main HEAD                       # rc 0, no conflict
$ python -m unittest <module>, one process each, for the 58 test modules that name the worker or the evidence manifest, push gate included
  52 rc 0 (test_runtime_worker_openhands_push_gate 127 OK, test_validate 86, test_evidence_manifest 5, test_local_pages_policy_grants 25)
  6 rc 1, each failing the same tests at d7ca288c: catalog_freshness_propose (model currency), local_pages_architecture_read_boundary and _sources (no /usr/bin/zstd on the host), secret_path_guard (the installed user-scope guard copy), token_e2e_grader (cross-device clone), runtime_profiles (no yaml in venv-ci; 32 OK under python3)

Fold round (738e24e)

The command center's verdict at 37dfd70 (CHANGES_REQUESTED: one P2 from the Claude security read, one P2 and six P3 from Muse) asked for B1 to B8 in one commit on 37dfd70.

Finding Fix Test; fail-first at 37dfd70 with only the test file overlaid
B1 (P2): the srt hold left 0.0.80's AF_VSOCK refusal unapplied on WSL2 srt 0.0.80, with "allowNestedUserNamespaces": true in Boundary.settings. SRT_VERSION, SRT_INTEGRITY and the tarball sha256 come from the registry (SOTA sources). BOUNDARY_PROBE gains a vsock line: it creates socket(AF_VSOCK) and never connects, and only EPERM passes. The README and the check's pass text name what the boundary refuses. The hold comment is replaced with the sources and the measurement. allowAllUnixSockets, which drops the seccomp helper, joins SRT_FORBIDDEN. test_the_pinned_srt_is_0_0_80_from_its_registry_tarball_with_nested_namespaces_allowed: '0.0.79' != '0.0.80'. test_the_review_runs_inside_srt_which_allows_the_api_host_only: the settings lack allowNestedUserNamespaces. test_the_boundary_check_passes_only_what_was_measured_closed: the 3 vsock subtests (opened, EAFNOSUPPORT, missing) give True is not false, and the pass text lacks socket(AF_VSOCK) refused.
B2 (P2): a relative CLAUDE_BIN was hashed from the worker's cwd and bound from srt's run directory settings_from refuses a relative HOME, and any path setting that is not absolute after ~ expansion (CLAUDE_BIN, state, ledger, upstream, srt, node). Settings refuses a relative claude_bin. test_a_relative_client_binary_is_refused_even_with_its_digest: 9 subtests, ConfigError not raised (a relative path with its valid digest among them).
B3 (P3): "no binary runs unchecked" was absolute wording for a check by path README (the CLAUDE_BIN_SHA256 lines) states that the check is by path: hashed at the tick and before each key, then bound by path. A writer with the worker's own uid could swap the file in between, and that uid can already edit worker.py. The settings_from comment no longer makes the claim. Wording; no test.
B4 (P3): claude_sha256=None switched every hash gate off Settings.claude_sha256 must be 64 hex characters, so None or a malformed value raises ConfigError, dataclasses.replace included. The three gates no longer skip on a missing pin. The test fixtures pass a real digest. test_settings_without_a_pin_are_refused: 5 failures, ConfigError not raised. The preflight test drops its (None, False) case.
B5 (P3): the per-key read-failure branch had no test test_a_binary_that_becomes_unreadable_before_the_next_key_starts_nothing_more Passes at 37dfd70, because the branch exists there; this closes a test gap. Mutation check: with a read failure treated as a pass (again = binary), it fails with Lists differ: ['anthropic-api-4', 'anthropic-api-3'] != ['anthropic-api-4'], while the mismatch sibling still passes.
B6 (P3): chmod(0) under root; an undebited refusal's receipt was overwritten; the log after a per-key stop (a) The unreadable-binary test makes the hash function raise instead of using chmod(0), so it holds under any euid, and it checks that the receipt's binary is null. (b) An undebited no_stream refusal is appended as an uncounted attempt (an identity check, is), so the next attempt gets its own number and directory. (c) When keys ran and were refused, the log names them and the check that stopped the next key. (a) Passes at 37dfd70: a robustness change that cannot fail as a non-root user. (b) test_a_refusal_before_any_debit_keeps_its_receipt_when_the_next_attempt_runs: Lists differ: [] != [(1, 'launch_failed', False)]. (c) test_a_binary_replaced_before_the_next_key_starts_nothing_more: True is not false on "did not start".
B7 (P3): "however it is spelled" overstated a lexical comparison The test comment and the settings_from comment state the lexical comparison. A subtest pins that a .. spelling of the default path needs its own digest. Passes at 37dfd70: the behaviour is unchanged and fails closed.
B8 (P3): 2.1.302's adoption was dated 2026-10-15 It now reads "2.1.302 since 2026-10-17". Wording.

Fail-first, overall: 107 tests, FAILED (failures=22, skipped=1), rc 1. All 22 failures are in the seven tests named above, and the other 100 tests pass on 37dfd70's source.

srt measurements: worker boundary_self_check, keyless, run through the real srt → bwrap chain with the retained 2.1.302 binary bound. Scratch installs of both registry tarballs are under /var/tmp/cc-cc-work/build1127-f-scratch/, never the worker's prefix.

srt settings probe result time (UTC)
0.0.79 as at 37dfd70 37dfd70's (no vsock line) pass: lo only, API through srt, the rest refused 12:08:38Z
0.0.79 as at 37dfd70 this head's fail: vsock: opened, not refused with EPERM; every IP line as before 12:08:35Z
0.0.80 without the opt-out this head's fail: network boundary check exit 1: bwrap: No permissions to create a new namespace 12:08:36Z
0.0.80 this head's (allowNestedUserNamespaces: true) this head's pass: interfaces lo; api 0 404 200; other 7 000 403; direct 6 000 000; loopback_direct 7 000 000; loopback_proxy 0 403 000; vsock refused EPERM 12:08:37Z

On the host itself, /dev/vsock is crw-rw-rw-. RealBoundaryChainTest (fake key, temporary store, stand-in client) also passes on the 0.0.80 install with this head's settings: the inner bwrap --unshare-user runs under the opt-out.

Commands at 738e24e (each run from the worktree with /var/tmp/cc-cc-work/venv-ci/bin first on PATH, without -I):

$ npm view @anthropic-ai/[email protected] dist --json      # rc 0; integrity sha512-WG4P8faU…MLpxnA==, dist-tag latest 0.0.80
$ curl -fsSL <dist.tarball> ; sha256sum                           # rc 0, rc 0; 7da66e1a…5f95, sha512 of the bytes = registry integrity
$ npm pack @anthropic-ai/[email protected] ; sha256sum       # rc 0, rc 0; anthropic-ai-sandbox-runtime-0.0.80.tgz, 7da66e1a…5f95
$ npm audit signatures --prefix <scratch srt80>                   # rc 0; 4 verified registry signatures, 1 verified attestation
$ measure_boundary.py x4 (table above)                            # rc 0 / 1 / 1 / 0 as listed
$ claude --help, retained 2.1.302 and 2.1.300, scratch HOME and CLAUDE_CONFIG_DIR   # rc 0, rc 0; cmp rc 0 (both sha256 223ec71c…c64e, 22110 bytes); all 17 claude_flags() flags present
$ probes.py offline, 2.1.302 and 2.1.300 (scratch HOME, XDG_CONFIG_HOME, state; srt 0.0.80 scratch prefix)  # rc 1 each: 15 PASS, only O6 FAIL (no key store in the scratch HOME, by design)
    O5 init on both: tools Glob, Grep, Read; mcp_servers 0; plugins 0; permission_mode dontAsk; api_key_source none
$ python3 -m unittest tests.test_claude_review_worker             # 107 tests OK (skipped=1), rc 0
$ CRW_TEST_SRT=<srt80> CRW_TEST_NODE=<node> python3 -m unittest …RealBoundaryChainTest …RealSandboxChainTest   # 2 tests OK, rc 0
$ (fail-first) the module on 37dfd70a's source with only the test file overlaid   # FAILED (failures=22, skipped=1), rc 1
$ (B5 mutation) the B5 test and its sibling on a mutant worker.py # FAILED (failures=1), rc 1: the B5 test only
$ python3 scripts/evidence_manifest.py --check                    # passed, rc 0
$ python3 scripts/validate.py                                     # rc 1: only the 9 model-currency date checks; 37dfd70a's tree gives the same 9, origin/main caf9ad7c passes (rc 0)
$ python3 -m unittest tests.test_runtime_worker_openhands_push_gate   # after the commit: 127 tests OK (skipped=1), rc 0
$ git fetch origin main ; git merge-tree --write-tree origin/main HEAD   # rc 0, rc 0; no conflict, tree 86ccd305…
$ validate.py and the worker module on that merge tree            # validate rc 0 (passed); 107 tests OK (skipped=1), rc 0

Comment fold (b61cb4a)

The command center's delta verdict at 738e24e (CHANGES_REQUESTED, two P3 from the Claude security read; Muse PASS) asked for one comment-only commit. No code or test logic changes.

Finding Fix
B1 (P3): the srt comment understated what allowNestedUserNamespaces lifts The comment at SRT_VERSION also cites vendor/seccomp-src/apply-seccomp.c:804-811 at v0.0.80: the helper's outer stub then skips PR_SET_NO_NEW_PRIVS as well as its namespace filter. With no helper, the opt-out drops bubblewrap's --disable-userns (src/sandbox/linux-sandbox-utils.ts:3844-3852). The command still gets PR_SET_NO_NEW_PRIVS (:943-945) and the socket filter. The v0.0.79 stub set neither (apply-seccomp.c:764-769 at v0.0.79), so nothing widens against it.
B2 (P3): "lexical" misdescribed the default-path test The settings_from comment and the settings test's comment say it is a pathlib Path comparison: it folds repeated slashes, . segments and a trailing slash, but not .. or a leading // (checked under Python 3.13.16 and 3.14).
$ TMPDIR=/var/tmp/u1127 python3 -m unittest tests.test_claude_review_worker   # 107 tests OK (skipped=1), rc 0
$ python3 scripts/validate.py                       # rc 1: only the 9 inherited model-currency date checks (origin/main caf9ad7c passes)
$ python3 scripts/evidence_manifest.py --check      # passed, rc 0
$ python3 -m unittest tests.test_runtime_worker_openhands_push_gate   # after the commit: 127 OK (skipped=1), rc 0
$ git fetch origin main ; git merge-tree --write-tree origin/main HEAD   # rc 0, rc 0; tree 682f325a…

Follow-ups

  • Closed in the fold round: CC FULL at d7ca288, P3-2, P3-3 and P3-4 (verdict B5 and B6).

  • docs/decisions/2026-10-09-claude-review-worker.md:431-432 and :448 still name srt 0.0.79 and its tarball. The record is outside this PR's owned paths, so it is left for the command center.

  • B6 (b) note: publish_pending posts only the last attempt's unsent result. An uncounted launch_failed attempt after a counted attempt that is still unposted (CLAUDE_REVIEW_POST=0) makes that one not the last, as a debited launch_failed attempt already did. The head is then reviewed again, and that result is posted.

  • Fix2 reads, local P2 (worker.py:102): the retained executable is selected by default before it is provisioned. Micro F5's install step owns this, and the worker is not enabled.

  • Fix2 reads, local P2 (worker.py:94-96): the cleanup correction lacks a pinned upstream citation.

  • Fix2 reads, Astra P2s (inherited): the model-currency age checks fail on this branch until the landing rebase, which main has already refreshed; the plugin-guard native verification is still outstanding.

  • GPT P2 (inherited): add cc-plugin-telemetry@builtin to the disabled built-in plugins and rerun the native probes; the function check covers tools, MCP and permission mode, not the zero-plugin invariant.

  • GPT P2 (inherited incident): the precomputed catalog-freshness test repeats the model-currency age failure.

  • Micro F4: no test pins the hash check before the sandbox self-check, and no tick-level mismatch test.

  • Micro F5: the remaining 2.1.299 text (worker.py:88-93, README:61 and :18-19) and an install step that creates the retained copy (a fresh host stops with "CLAUDE_BIN is not a file").

  • Micro F6: the ledger client and receipt binary.name now read "claude"; file_sha256 duplicates sha256_file.

  • GPT fix1 P3: the default-path spelling support is narrower than the Notes state, and the logic corrections of round 1 carry no pinned upstream mechanism citation.

Notes for the reads

  • The worker is not enabled yet; Re-pin the review worker's Claude Code client to 2.1.299 #1107's live probes come first. At the head this PR changes the default binary and its preflight check, moves srt to 0.0.80 with allowNestedUserNamespaces in its settings, adds the vsock line to the keyless boundary check, refuses settings without a sha256 pin or with a relative path, and records an undebited refusal as an uncounted attempt.
  • CLAUDE_BIN_SHA256 overrides the digest. A CLAUDE_BIN override naming another path needs it, and the default path keeps the built-in digest. The test is a pathlib Path comparison after ~ expansion: it folds repeated slashes, . segments and a trailing slash, but not .. or a leading //, so such a spelling needs its own digest (fold round B7, corrected in the comment fold).

Checklist

  • No GitHub Actions changed.
  • No workflows changed.
  • No secrets printed, logged or committed.
  • No paid hosting, subscription or billing surface.
  • Peer-owned files and worktrees preserved.

🤖 Generated with Claude Code

… 2.1.299

Native auto-update is on here (claude doctor, 2026-10-15T00:44:57Z: Auto-updates
enabled, channel latest). The native installer deletes old versions from
~/.local/share/claude/versions/, keeping only the launcher's version, any
version a running session uses and the two newest
(https://code.claude.com/docs/en/setup, home directories on network
storage). So the worker's pinned versions/2.1.299 path can vanish at the
next release.

- DEFAULT_CLAUDE_BIN is the command center's retained copy outside
  versions/, ~/.local/share/claude-retained/2.1.299/claude.
- DEFAULT_CLAUDE_BIN_SHA256 is 2d894a4b..., the linux-x64 checksum of the
  GPG-verified signed 2.1.299 manifest (manifest sha256 5052df4b...; good
  signature, fingerprint 31DD DE24 DDFA B679 F42D 7BD2 BAA9 29FF 1A7E CACE).
  The retained copy and versions/2.1.299 both hash to it.
- Preflight hashes the client binary at every tick and refuses one that
  differs, before any key is read. CLAUDE_BIN_SHA256 overrides the digest; a
  CLAUDE_BIN override is checked only when it is set. A malformed digest is
  a ConfigError.
- The vendor's other option, a custom launcher (which keeps every version),
  is not taken: it would change the binary every other session runs.

Tests: 96, OK (1 skipped, as on main). Against main's worker the settings
test fails and the new preflight test errors in all three subtests. The
real retained binary passes the check through file_sha256 (0.24 s).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Oct 15, 2026
On 2026-10-14 the owner directed (paraphrased, through the command center)
that the headless roles follow the latest Claude Code: pinned roles move
to each release through a retained, hash-checked copy after a function
check of their exact invocation, not a paired quality run.

- DEFAULT_CLAUDE_BIN is ~/.local/share/claude-retained/2.1.300/claude.
  DEFAULT_CLAUDE_BIN_SHA256 is 674deba0...5453, the linux-x64 checksum of
  the GPG-verified signed 2.1.300 manifest (manifest sha256 fb62dae2...).
  The retained copy and versions/2.1.300 both hash to it. The preflight
  check is unchanged.
- Function check, 2026-10-15, run against retained 2.1.299 and 2.1.300:
  - claude_flags(): every flag is present in both --help texts. The only
    change is --restricted's description, which drops REPL from the tools
    it removes; the worker passes --tools Read,Glob,Grep.
  - The exact flag list, run with no key from a scratch config dir, gives
    the same init record on both: tools Glob, Grep and Read, no MCP
    server, permission mode dontAsk.
  - The offline probe suite could not run on this host: srt 0.0.79 is not
    installed, since the worker is not enabled yet. That stays #1107's
    live-probe step before enabling.

Tests: 96 OK (1 skipped, as on main). With the previous worker.py the
settings test fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@seathatflowsinourveins seathatflowsinourveins changed the title Run the review worker on a retained, hash-checked copy of Claude Code 2.1.299 Run the review worker on a retained, hash-checked copy of the latest Claude Code (2.1.300) Oct 15, 2026
seathatflowsinourveins added a commit that referenced this pull request Oct 15, 2026
On 2026-10-14 the owner directed (paraphrased, through the command
center) that the headless roles follow the latest Claude Code. The floor
addendum, the pins' notes and the client-core note no longer say that
the review worker and the trading runner stay on 2.1.299 pending a paired
check.

They now state the rule: a pinned role runs a retained copy outside
versions/, checks its sha256 against the release's signed manifest at
launch, and moves to a new release after a function check of its exact
invocation, not a paired quality run. The retained copy protects the
role against the native installer's version cleanup and against a broken
release. At this addendum both roles move to 2.1.300: the trading runner
by its own dated copy, the review worker in #1127.

Derived records regenerated: final-stack.json, the snapshot label, the
evidence registry. Tests: the affected modules pass (bootstrap 79 and
153, final_stack 75, handbook 88, slot checks, validate 86, push gate
127).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…read errors; recheck the pin at the run

Fix round 1 for #1127 (GPT full1 blocker 1, CC micro F1-F3, GPT P3-3 and P3-4):

- F1 / blocker 1: settings_from() raises ConfigError when CLAUDE_BIN
  names another path than the default and CLAUDE_BIN_SHA256 is unset or
  empty, so the command line exits 2 before a worker, a launcher or a
  GitHub call exists. The default path, however it is spelled, keeps the
  built-in digest. An explicit path/digest pair still works.
- F2 / P3-3: an OSError from the hash read (unreadable, or removed after
  the is_file check) is a "not ready" problem on the tick's own record
  path: a log line and a ticks.jsonl row, not a traceback.
- F3: run_keys() compares the run's re-hash with the pin before its
  first debit; a binary replaced after the tick's check starts nothing
  and debits nothing (no_stream: not counted, the tick stops).
- P3-4: the README records the rollback to the retained 2.1.299 copy
  (path and the 2.1.299 release manifest's linux-x64 checksum, with the
  checks before enabling) and its undo; the override note now says an
  override needs its digest. The test comment names 2.1.300.

Fail-first: the four new or changed tests failed at e6046ea (7
failures/errors, each for the defect: no ConfigError, None digest, a
worker built, a PermissionError traceback, a launch and two ledger rows
after the binary changed) and pass here; the module runs 99 tests OK.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
GPT read of #1127 fix1 at 795232e (P2), the same finding as the local review's P2 at worker.py:2054-2055:
binary_identity() cached the digest by (path, size, mtime_ns), so a replacement keeping size and
mtime (shutil.copystat copies nanosecond timestamps: python/cpython@cbc944f4 v3.13.16
Lib/shutil.py:348,385) passed the pin check with the old digest. run_keys() now calls
binary_identity(fresh=True), which hashes the file every run, and refuses with nothing debited when
the file cannot be read.

Regression test_a_replacement_keeping_size_and_mtime_is_caught_by_a_fresh_hash warms the cache with a
first review, replaces the binary with same-size bytes at the same mtime_ns, and expects no second
launch or debit. Fail-before: Tuples differ: (2, ['debit', 'settle', 'debit', 'settle']) !=
(1, ['debit', 'settle']). After: tests.test_claude_review_worker 100 tests OK (skipped=1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…rvives an unreadable binary

CC adjudication of #1127 at 0fb64d8 (the final round):
- (a) both families' P2 (worker.py:2056-2057): the run hashed the binary once, so a binary replaced while key 1
  was refused for credit was launched under key 2. Before each further key, run_keys hashes the file again; a
  mismatch or a read failure stops the run before the switch row, the debit and the launch.
  test_a_binary_replaced_before_the_next_key_starts_nothing_more, fail-before: Lists differ:
  ['anthropic-api-4', 'anthropic-api-3'] != ['anthropic-api-4'].
- (e) Astra P3s: the read-error refusal of fix 2 gets its own test, and the receipt's binary field reads the file
  through readable_binary_identity(), so a binary that stays unreadable no longer escapes while the refusal's
  receipt is written. test_a_binary_that_cannot_be_read_at_the_run_is_a_recorded_refusal, fail-before:
  PermissionError: [Errno 13] Permission denied.

After: tests.test_claude_review_worker 102 tests OK (skipped=1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…302; hold srt at 0.0.79

Claude Code 2.1.302 is the latest release. Its signed manifest
(https://downloads.claude.ai/claude-code-releases/2.1.302/manifest.json,
sha256 d7d4f193bfebb48733027b0dc43d6996d99f5335d152961a729730e55bbd5aa1)
verifies with gpg against https://downloads.claude.ai/keys/claude-code.asc
(fingerprint 31DD DE24 DDFA B679 F42D 7BD2 BAA9 29FF 1A7E CACE) in an empty
keyring. Its linux-x64 checksum, 60d7eb7f...29a5, matches the installed
versions/2.1.302 and the retained copy under claude-retained/2.1.302.

Function check from 2.1.300: the two `--help` texts are byte-identical,
and the exact claude_flags() list, run with no key from a scratch config
directory, gives the same init record (tools Glob, Grep, Read; no MCP
server; no plugin; permission mode dontAsk). The CHANGELOG entries for
2.1.301 and 2.1.302 (anthropics/claude-code@03b5892e and @c017428c)
change none of the flags, settings or the permission mode the worker uses.

The README's rollback pair moves to 2.1.300 (674deba0...5453, size
266972344, from its signed manifest).

srt stays at 0.0.79. 0.0.80 (anthropics/sandbox-runtime@626a5967) refuses
the sandboxed command new user namespaces by default (9a277e5, #588;
README.md:715 at the tag), and the worker's inner bwrap --unshare-user
then stops: boundary_self_check passes on 0.0.79 and fails on 0.0.80.
Whether to adopt the allowNestedUserNamespaces opt-out is the command
center's decision; worker.py records the finding next to the pin.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@seathatflowsinourveins seathatflowsinourveins changed the title Run the review worker on a retained, hash-checked copy of the latest Claude Code (2.1.300) Run the review worker on a retained, hash-checked copy of the latest Claude Code (2.1.302) Oct 17, 2026
…ck; absolute, pinned client paths

Fold of the CC verdict on #1127 at 37dfd70 (B1 to B8).

B1. srt moves to 0.0.80 (anthropics/sandbox-runtime@626a5967, tag v0.0.80).
Its seccomp helper refuses socket(AF_VSOCK) with EPERM
(vendor/seccomp-src/seccomp-unix-block.c:212-229; #691; absent at v0.0.79),
and a network namespace does not fence VM sockets on WSL2. Boundary.settings
sets allowNestedUserNamespaces (README.md:592, example :445-446;
src/sandbox/sandbox-config.ts:1179-1187), which lifts only the namespace
filter and the max_user_namespaces limit (apply-seccomp.c:887-888, :954-958),
so the inner bwrap keeps its user namespace and the socket filter stays.
Pinned from the registry: integrity sha512-WG4P8faU...MLpxnA== (npm view
dist), tarball sha256 7da66e1a5161bbcb57cf8a6f7ba7d85f1e6bb7897040b295885dc74c1a255f95,
the same from npm pack; npm audit signatures verifies it.
boundary_self_check gains a vsock line (socket creation only, never a
connect; only EPERM passes). Measured keyless on 2026-10-17: 0.0.79 fails
on vsock alone (the socket opens inside the chain); 0.0.80 without the
opt-out fails ("bwrap: No permissions to create a new namespace"); 0.0.80
with it passes, vsock refused EPERM. allowAllUnixSockets, which drops the
helper, joins SRT_FORBIDDEN. The README and the check's pass text say what
the boundary refuses.

B2. settings_from refuses a relative HOME and any path setting that is not
absolute after ~ expansion; Settings refuses a relative claude_bin, so the
hashed file and the bound file are one.

B3. README states that the hash check is by path, with its same-uid bound.

B4. Settings requires a 64-hex claude_sha256 (None or malformed raises
ConfigError), and the three hash gates no longer skip on a missing pin.

B5. A test for the per-key read-failure branch (again is None).

B6. The unreadable-binary test raises from the hash function instead of
chmod(0), which root ignores. An undebited refusal is recorded as an
uncounted attempt, so the next attempt gets its own number and receipt.
After a per-key stop the log names the key that ran and was refused.

B7. The override test states the lexical comparison and pins a .. spelling.

B8. The sha256-check test dates 2.1.302's adoption 2026-10-17.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Comment-only fold of the CC delta verdict on #1127 at 738e24e.

B1. The srt pin comment also cites apply-seccomp.c:804-811 at v0.0.80:
under allowNestedUserNamespaces the helper's outer stub skips
PR_SET_NO_NEW_PRIVS as well as its namespace filter, and with no helper the
opt-out drops bubblewrap's --disable-userns
(linux-sandbox-utils.ts:3844-3852). The command still gets
PR_SET_NO_NEW_PRIVS (:943-945) and the socket filter. The v0.0.79 stub set
neither (apply-seccomp.c:764-769 at v0.0.79), so nothing widens against it.

B2. settings_from's comment and the settings test's comment say the
default-path test is a pathlib Path comparison: it folds repeated slashes,
`.` segments and a trailing slash, but not `..` or a leading `//`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@seathatflowsinourveins
seathatflowsinourveins merged commit cdaf31f into main Oct 17, 2026
34 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/cc-native-practice-retained-2299-pin-20261015 branch October 17, 2026 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant