Repository navigation
#381 PR-A: token-adoption measurement kernel (M3/M4/M5, RTK eligibility, MCP attempt states) - #432
Merged
Merged
Conversation
seathatflowsinourveins
force-pushed
the
claude/w3-measure-20260927
branch
2 times, most recently
from
September 27, 2026 21:23
7aba17a to
f3ec648
Compare
Add M3/M4/M5, native RTK part eligibility and explicit-wrap guards to the existing child usage tool; reuse its kernel from the Codex skill usage tool. Report main actors separately, retain reviewed digest-bound exceptions, count nested fetches and unknowns, and preserve per-message/cumulative usage. Keep stdout hook claims separate from inserted context and retain MCP states. Sources: #381 token-adoption-e2e-20260926 preregistration and RUNBOOK; rtk-ai/rtk v0.50.0 (1d87b8e719ce0a50c223cd93ca64dd16921f9aec), src/main.rs, src/discover/lexer.rs and registry.rs; mksglu/context-mode v1.0.169 hooks/posttooluse.mjs, hooks/core/routing.mjs and src/session/extract.ts; ccusage/ccusage v20.0.24 (ecb676cce27cb5dd0090c7804a5cecc35e8ba805) Claude daily and Codex parser; openai/codex rust-v0.157.1 protocol.rs and models.rs. Validation: 117 covering unittests pass, including the Node synthetic suite and installed native RTK hook-check controls. Publication validation reaches only stale evidence hashes/byte counts; the coordinator owns their refresh. Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute pool; committed by the coordinator harness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep quoted operators out of redirect guards, preserve native RTK eligibility, separate conditional log/find review, and exclude proxy parts from coverage. Normalize Codex custom/local-shell calls and keep code-mode sandbox results out of context bytes. Fix text-block sizes, HTTP data detection, sweep populations, digest-binding counts and Codex usage shape. Sources: rtk-ai/rtk v0.50.0 (1d87b8e719ce0a50c223cd93ca64dd16921f9aec), src/discover/lexer.rs and registry.rs; mksglu/context-mode v1.0.169, hooks/core/routing.mjs:727-804 and src/session/extract.ts:1060-1069; openai/codex rust-v0.157.1, protocol/src/models.rs:1060-1165, app-server-protocol/src/protocol/v2/item.rs:333-352 and core/tests/suite/code_mode.rs:721-760,3436-3752; repository #381 preregistration/RUNBOOK and adopted conditional RTK exceptions. Verification: 130 covering unittests passed; 92 Node checks passed; 13 workflow bundle checksums passed; git diff --check passed. New controls reproduced defects before fixes. Publication validation reports inventory binding mismatches; the manifest is outside this repair's write scope. No live-provider or unchanged-upstream-suite acceptance is claimed. Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute pool; committed by the coordinator harness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute pool; committed by the coordinator harness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Content written by GPT-6 (gpt-6-astra, effort max) through the Codex worker lane and the OmniRoute pool; committed by the coordinator harness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fixup3 for unit measure (#381 PR-A), repairing the Opus verification of fixup2 (verdict block). D1 (high): a heredoc body is source only when the simple command around its << operator runs stdin as source: the text between the nearest control operators, quotes removed, redirections and their targets dropped. Shells follow POSIX.1-2024 sh STDIN and bash(1) 5.2 OPTIONS/ARGUMENTS (-c or a script operand keeps data; -s keeps stdin; -o/+o/-O/+O take a name; --rcfile/--init-file take a file; -n does not execute). ssh follows OpenSSH ssh(1): stdin runs in the remote login shell without a remote command, or when the remote command reads stdin. A << inside quotes, a comment or $(( )) opens no heredoc; one inside a quoted string a shell runs is left to the quoted-string recursion. fetchKind and the legacy bash_curl_wget lane follow the same rule again. The misleading stdin-selection comment is replaced. Recommendation 6: raw curl/wget in command position and raw gh api matches that executed-text analysis does not confirm join HTTP_SCRIPT misses in fetch_mentions_unconfirmed (both READMEs). D3 (low): executed text keeps each character's raw offset; a confirmed match accounts only for the raw match at its own offset, so matches created by joins, unescaping or run quoted strings cannot offset a raw miss. The verifier's backslash-newline control and a no-whitespace join control are included. D2 (medium): SHA256SUMS regenerated with the documented sha256sum -- *.mjs *.js *.json (README.md removed; 13 entries). D4 (medium): units/w3/measure/pr-body.md is now the single whole-branch body. Evidence: the verifier's reproduction held at 71c0a991 and e7d3c5a3; after the fix rows 1-4 give "fetch" 1 0 0, row 5 null 0 1 0 and row 6 null 0 1 1. The five new tests failed first against the 71c0a991 module (145 subtests) and now pass: test_token_measurement 38 OK with no skips, covering modules 143 OK, Node suites 92/92, 254/254 and 74/74. A differential run kept 171 of 198 corpus commands byte-identical (the 27 others are the new controls); fuzz 80,000/0 mismatches; trace integrity 1,028,897 characters/0 violations. Full suite: Ran 6685 tests, failures=2, errors=1, skipped=761: the two known /tmp-worktree environment failures and the publication-validator test on the same inventory mismatches, nothing else. validate.py exits 1 only on SHA/byte mismatches of the six re-registration paths. Sources: #381 preregistration.json M4; mksglu/context-mode v1.0.169 (589d8214d56740a28b5f7bf63167743d586b0b40) hooks/core/routing.mjs:228-229 and 727-804; POSIX.1-2024 utilities/sh.html; GNU bash 5.2 bash(1); OpenSSH ssh(1) (man.openbsd.org/ssh, installed 9.6p1). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tch notes out of the tree The Opus re-check of fixup3 reproduced a conservative false positive: `$(` is not tracked inside double quotes, and a quoted string a shell runs is added without its own heredoc resolution, so a data heredoc in `git commit -m "$(cat <<'EOF' ...)"` or `bash -c '...'` counts as executed. The README now states that limit instead of the wrong rule; the fix follows in its own PR. The builder's handoffs, receipts and PR body under units/w3/measure/ were scratch work products, not repository content, and leave the tree; the hot-file commit drops their registrations. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…alerts CodeQL reported four exponential-backtracking alerts on this branch: - RUN_HTTP_CODE (node, bun, deno eval): the option word --?[\w-]+ split '---' two ways. -[\w-]+ matches the same words with one reading. - logFindPart: the git option prefix could read '--git-dir' either as an option taking the next word or as a lone --option. The git prefix is now walked over word positions: every reading of the option words, then the rest of the pattern at each word it can reach. The same walk evaluates the two RTK exclude_commands patterns that share the prefix. Their strings, which the RTK config is written from, are unchanged. A seeded check compared old and new over 4,167,264 generated inputs, with 0 mismatches. The CodeQL witness strings at 5,000 repetitions now take under 7 ms. New tests fail against the old module (2 of 95: 28 repetitions exceed 1 s) and pass against the new one (95 of 95). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins
force-pushed
the
claude/w3-measure-20260927
branch
from
September 27, 2026 21:43
f3ec648 to
aff00e9
Compare
This was referenced Sep 27, 2026
Closed
seathatflowsinourveins
added a commit
that referenced
this pull request
Sep 29, 2026
…er-bash parser (#516) * Add failing-first N1 probes and must-stay controls for M4 N1 is the #432 fixup3 residual: openers() does not track "$(" inside double quotes, and a quoted string that a shell runs is appended without its own heredoc resolution, so heredoc data reads as executed. Adds three tests through the existing measure/exports bridges on all three shell carriers plus fetchKind: - p1 (git commit -m "$(cat <<'EOF' ...)" with a line-start curl), its body variants with ', " and ( ), p2 (gh api in the body), p3 (bash -c 'cat <<EOF > x.sh ...') and a double-quoted p3 variant; - must-stay controls: an executed curl in "$( )", a shell heredoc in "$( )", an escaped substitution in a double-quoted run string, the command after a heredoc in "$( )" (guards against resolving one heredoc twice), the outer-shell heredoc in a double-quoted run string, unquoted $( ) and the "Subject (scope)" body; - the nested-quote case from design 1.5. At cf3fb72e the new tests fail 23 subtests (p1, the ' variant, p3 and the double-quoted p3: shell fetch 1 != 0 and fetchKind 'fetch'; p2: unclassifiable 1 != 0; nested quotes: 0 != 1 and fetchKind None); the must-stay test passes. Source: POSIX.1-2024 XCU 2.6.3 Command Substitution, checked with GNU bash 5.2.21 and dash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first N1 tests for strings a shell runs The N1 fix analyzes a string that a shell runs (RUN_QUOTED) as that shell's input in both modes, instead of appending it verbatim unless inlineHttp is set. Two consequences get their own tests first: - in a double-quoted run string the outer shell removes the backslash before " (POSIX.1-2024 XCU 2.2.3), so `bash -c "echo \"a; curl ...\""` runs only echo: base reads a confirmed shell fetch; - a string the inner shell runs in turn is analyzed too, so the curl in `ssh host 'bash -c "curl ..."'` is confirmed: base counts it in neither the confirmed nor the unconfirmed fetches, so the gate's lower bound could not see it. Against the cf3fb72e kernel the test fails 7 subtests (1 != 0 and 0 != 1 on each carrier, fetchKind ['fetch', None]). GNU bash 5.2.21 and dash print `a; echo RAN` for the escaped form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve heredocs inside "$( )" and in strings a shell runs (N1) Fixes the #432 fixup3 residual N1 in child-usage.mjs. POSIX.1-2024 XCU 2.6.3: inside double quotes the text from "$(" to the matching ")" is tokenized recursively; 2.2.3: in a double-quoted word the outer shell removes the backslash before $ ` " \ and newline. GNU bash 5.2.21 and dash agree on every shape below. - step() is one frame machine (', ", `, and $( with depth) shared by phase 1 openers() and the phase 2 closeQuote()/matchParen(), so the phases agree on where a quote or substitution ends. With no frame open it reads exactly as the old openers(); only a double-quoted word gains $( and backquote frames, so a << inside "$( )" is found and its body resolved like any other heredoc. - executedTrace splits into resolveHeredocs (phase 1) and scanQuotes (phase 2). The naive end-of-quote scan becomes closeQuote; a "$( )" body in double-quoted data is spliced as "$(" + scanQuotes(body) + ")" with exact offsets (no second phase 1 over it). - A string a shell runs (RUN_QUOTED) is analyzed with the full executedTrace in both modes, a double-quoted one after unquoted() removes the outer shell's escapes at its own level. A shared set of resolved operator offsets keeps a heredoc the outer shell resolved in "$( )" from reading later lines as its body again. - NESTING_LIMIT (32) bounds recursion: deeper analyses read as data. Without it '"$(' x 3000 and a 3000-deep shell heredoc chain throw RangeError, which would abort a sweep. - The paren-free DOUBLE_QUOTED_DATA regex is removed; the scanners are linear character scanners (no new regex; CodeQL js/redos). The failing-first tests from 1d206a12 and 54299c44 now pass; the must-stay controls are unchanged. SHA256SUMS is regenerated in a later stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document the N1 reading and add kernel unit cases The workflows README replaces the N1 limit paragraph with the rule the kernel now follows: "$( )" inside double quotes is read recursively (POSIX.1-2024 XCU 2.6.3), a string a shell runs is analyzed as that shell's input after the outer escapes are removed (XCU 2.2.3), and reading text as data moves a raw detector match to the unconfirmed count instead of dropping it. It states the remaining limits: backquoted spans inside double quotes, case patterns inside "$( )", the 32-level nesting bound, and text bash rejects as incomplete. tools/skill-usage/README.md notes that the legacy Python executed_text rule predates this reading and can disagree on these shapes. test-child-usage.mjs adds fetchKind and exact executedText cases for the N1 shapes and a nesting witness. Against the cf3fb72e kernel the three new cases fail (95/98); with NESTING_LIMIT removed the witness fails (97/98, RangeError); with the fix all 98 pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * State how the N1 reading moves the M4 lower bound The previous wording said reading text as data leaves the denominator of routed_share_lower_bound unchanged. That holds for raw detector matches only. Measured against the cf3fb72e kernel: - `echo "$(echo "a" && curl http://127.0.0.1:9/)"` moves from unconfirmed to confirmed loopback, which leaves the denominator; - `echo "$(\curl ...)"` gains a confirmed fetch no raw detector sees; - `echo "$(echo " ; \curl ...")"` loses a confirmation the old reading created (bash prints it as data), so the bound can rise. The README now states those three cases. It also adds a limit: a backquoted span outside quotes is still read as plain command text, so a quote it leaves open runs past its closing backquote. test-envelope (254/254), test-usage-receipts (18/18) and test-contract-mutations (74/74) pass; they read this README as routing_doc and contract_doc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first CLI-lane, state and proxy-position fixtures Add the U1 design section 7 fixtures before the kernel change. The Python integration cases feed measureTranscript, aggregateMeasurements and the sweep CLI: the stack.json lane commands (:1003, :797, :579, :762, :519, :629, :280, :407), wrappers, compound commands and substitutions, runners, every mcporter call form, negatives (data, lookups, registrations, near names, gcm and serena-hooks), version and help, remote and unresolved programs, the call states, the rtk proxy population by command position with prefix_rule_calls, M3 and M4 by_carrier, aggregation with actors_with_success, and ID-free sweep output. The node suite adds commandInvocations unit cases. Sources: #381 AA-PLAN PR-A item 3 and M6/M14; POSIX.1-2024 XCU 2.9.1-2.9.4 and 2.6.3; rtk-ai/rtk@1d87b8e7 src/main.rs:68-90,708-713,3008-3042 and the installed rtk 0.50.0 (proxy -- and --skip-env bind, -v after proxy is the program); openclaw/mcporter@93e0916c src/cli.ts:115-364, src/cli/command-inference.ts:10-95, call-arguments.ts:79-233 and call-command.ts:114-173,309-348 (a first positional with = is the selector). Before the fix: 10 Python tests fail (102 subtests) and the 8 new node cases fail on the missing export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count CLI lanes and rtk proxy calls by command position Export commandInvocations: every simple command of the executed text (POSIX.1-2024 XCU 2.9.1-2.9.4, 2.6.3) is resolved past reserved words, assignments, wrappers (timeout, env, nice, stdbuf, command, exec, time, nohup, sudo 1.9.15p5, xargs), package runners and rtk proxy to a lane executable by exact basename. mcporter operations and a call's downstream server follow openclaw/mcporter@93e0916c (cli.ts, command-inference.ts, call-arguments.ts, call-command.ts); HTTP and stdio selectors read (http) and (stdio), and no host is emitted. rtk proxy follows rtk-ai/rtk@1d87b8e7 src/main.rs:68-90,708-713,3008-3042 and the installed rtk 0.50.0 (no shell runs the proxied program). One memoized reading per call drives carrierOf (M3 and M4 by_carrier), measurement.proxy (adds invocations, nested, in_ctx_code and prefix_rule_calls) and the new measurement.cli_lanes with per-call states (AA-PLAN "Successful" and M14; not_executed from observed transcript markers). aggregateMeasurements sums the fields and adds actors_with_success; the sweep limits gain the static limits. executedTrace takes an optional marks collector for ssh-run text and data-heredoc substitutions; its output is unchanged (0 differences in executedText and fetchKind over 24,489 inputs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read mcporter URL and stdio selector forms with char scanners Replace the character-class regexes in httpUrl and httpToolSelector, the path-prefix alternation for an ad-hoc stdio selector and the blank split of an inline npx server with character checks and the existing shellWords splitter. The brief asks for linear character scanners rather than regexes in this kernel (CodeQL js/redos flagged it before). Behaviour is unchanged: the rules still follow openclaw/mcporter@93e0916c src/cli/http-utils.ts:1-69, call-argument-values.ts:68-83 and ephemeral-target.ts:134-158, and the node suite passes unchanged (107/107). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document CLI lanes, call states and the command-position proxy rule Add a CLI-lanes subsection to the PR-A measurement fields: the lane executables with the gcm and serena-hooks exclusions, the resolver (wrappers, runners, rtk proxy), each cli_lanes field, the mcporter operation and downstream-server precedence of mcporter v0.14.1, the call states with the observed not-executed markers and their M14 mapping, the static limits, and the changed meanings of proxy.calls, M3 by_carrier.rtk_proxy and m4.by_carrier with prefix_rule_calls kept for #369-era comparison. Record the decision that proxy.in_ctx_code stays outside M6 (Gate A plan M6 and rtk rows) and state that rtk_parts, proxy_parts and model_typed keep their prefix rules. The skill-usage README points Codex readers to the same fields and states that declined items and legacy exec headers still read succeeded until the adapter maps them. Sources: rtk-ai/rtk v0.50.0 src/main.rs:68-90,3008-3042; openclaw/mcporter v0.14.1 src/cli and docs/call-heuristic.md; mksglu/context-mode v1.0.169 src/exit-classify.ts:15-33; POSIX.1-2024 XCU 2.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a failing case: exec -- reads as an unresolved program U1 design 2.2 reads only the POSIX exec form and marks any word that starts with '-' after exec as unresolved, because bash's exec options are not pinned. The shells disagree on `exec -- cmd`: GNU bash 5.2.21 runs cmd, while dash tries to execute "--" (probe on this host). The kernel currently takes `--` as the end of options and reads qmd. Observed before the fix: "exec -- qmd mcp" => ["qmd/qmd"]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read any dash word after exec as an unresolved program Follow U1 design 2.2: only the POSIX exec form is read, so a word that starts with '-' after exec, `--` included, leaves the program unresolved. bash 5.2.21 runs the command after `exec --` while dash executes "--" itself, so neither reading is safe to assume. The failing case from 1fcc9f23 now passes (node suite 107/107). Also cover the inline npx server of an mcporter call (openclaw/mcporter@93e0916c src/cli/ephemeral-target.ts:134-158): a --server value that is an npx command line is an ad-hoc stdio server, and another blank-holding name reads (other). These cases were added after the implementation, so they are coverage, not failing-first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first Codex fixtures for shell call states U1 design 6 fixtures (i)-(vii) run rollout records through measure_codex_records, the Node bridge and the kernel's cli_lanes and proxy. Record shapes follow openai/codex rust-v0.157.1 (36650394): CommandExecutionItem and its snake_case status (protocol/src/items.rs :199-285), exec end states (core/src/tools/events.rs:529-573: a rejection is declined with exit -1) and the unified exec response header (core/src/tools/context.rs:524-575). A rollout output never carries the success flag (protocol/src/models.rs:2173-2182). Before the adapter change 6 of 8 subtests fail: - (iii) code-mode item declined: qmd succeeded 1, expected failed 1 and not_executed 1; the same for a direct call's declined item; - (iv) legacy exec_command with "Process exited with code 1": rtk_proxy succeeded 1, expected failed 1; - (v) "Process running with session ID 3": qmd succeeded 1, expected unknown 1; - (vi) no header and no item: qmd succeeded 1, expected unknown 1; - (vii) local_shell_call running stack.json:280: codebase-memory-mcp succeeded 1 and downstream codebase-memory succeeded 1, expected unknown 1 in both. (i) nested rtk proxy (proxy.calls 1, proxy.nested 1, succeeded 1) and (ii) a failed item already pass. Must-stay controls pass: an exit 0 header, an exit line inside the output, a completed item that beats a running header, a failed item with its header, and the aggregate passing cli_lanes and proxy through with actors_with_success. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Map declined items and exec headers to Codex call states U1 design 6: the Codex adapter now hands the kernel's callState what a rollout shows about a shell call's outcome, so cli_lanes stops reading declined or failed legacy commands as succeeded. - A declined CommandExecution (openai/codex rust-v0.157.1 36650394 core/src/tools/events.rs:562-573, exit -1) sets is_error and native_state "declined" on both result paths: the call's own function_call_output and the item's aggregated output. The kernel counts it failed and not_executed. - A Bash-mapped call (exec_command, shell_command, shell or local_shell_call, chosen by call kind, never by output text) with no persisted item state is read by the unified exec header of core/src/tools/context.rs:524-548, and only by the lines before "Output:", in a linear line scan with a strict ASCII exit code. Exit 0 is success, another exit is_error. A running line (it wins over an exit line, unified_exec/process_manager.rs:1066-1071), a header with neither line or text without the header sets native_state "unknown": a rollout output never carries success (protocol/src/models.rs:2173-2182), and legacy history mode keeps no item (rollout/src/policy.rs:94-112). No shell, shell_command or local-shell handler exists at that revision, so those outputs read unknown unless an item state decides; apply_patch's "Exit code: N" text is not read. - codex_call_name shares the MCP namespace rule between the call loop and the shell-call set, so an MCP tool named exec_command is not a shell call. The failing-first fixtures from 4a7aa75a now pass (the three Python suites: 160 tests OK). The header unit test (test_exec_header_state_reads_only_the_pinned_header) was written after the parser and is coverage, not failing-first. The lanes CLI still writes nothing to stderr, and the Codex aggregate passes cli_lanes and proxy through unchanged. tools/skill-usage/README.md replaces the stage B sentence that said these calls still read succeeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate workflow SHA256SUMS for the U1 kernel and tests The N1 fix and the CLI-lane reading changed child-usage.mjs and test-child-usage.mjs; stage C leaves the kernel byte-identical to 0c421c66 (sha256 acc7bb51...). Regenerated with the documented command, `sha256sum -- *.mjs *.js *.json > SHA256SUMS` in examples/claude-native/workflows, and `sha256sum --check --strict SHA256SUMS` passes for all 13 files. Only those two digests change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-register the U1 files in manifests/evidence.json The hot-file protocol (docs/lanes.md:94-103) puts the shared manifest edit in the branch's last commit and re-registers every changed file that the manifest already lists, with host_receipts.register_file. scripts/validate.py checks SHA-256 and byte counts for each of them and failed before this commit ("SHA-256 mismatch", "byte count mismatch" for the workflows README, child-usage.mjs, test-child-usage.mjs, tests/test_token_measurement.py and tools/skill-usage/README.md). The eight re-registered entries are the two READMEs, SHA256SUMS, child-usage.mjs, test-child-usage.mjs, skill_usage.py, test_skill_usage.py and test_token_measurement.py. Only their sha256 and bytes change, and validate.py now passes (status passed, 7986 hashed files). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for the U1 pivot loader and M4 repairs Tests only, run against 7091a300 before any fix (U1 pivot brief D1, D6-D9, D11; GPT-6 findings #8, #9, #11, #12, #13; Claude review R1, R2, R3, R9). - D1 loader (test-child-usage.mjs, test_skill_usage.py): pin file contents, loadShellParser statuses (installed, not_installed, hash_mismatch), directory order, --shell-parser, commandInvocations null and cli_lanes parser_unavailable with proxy prefix_fallback, aggregate statuses, withShellTree freeing its tree, and the Node bridge awaiting the loader. - D6: shell_script quotes argv with shlex.join, and -c is a script only for a shell; exec_header_state bounds the exit code to 9 digits (GPT-6 #11, #12 inputs verbatim). - D7: outer-shell substitutions in a double-quoted run string are executed text (GPT-6 #8 verbatim), with single-quote, comment, backquote, sh, eval and ssh variants and must-stay controls that count once, not once per view. - D8: n = 8000..64000 doubling for unclosed ((, $((, "$( ((, and ( << E runs, at most 2.5x per doubling and under 150 ms at 64000. - D9: the stress check lets an exception fail it, includes an unquoted '$('.repeat(3000) input (NESTING_LIMIT), and a mutation control (a reader that throws for long input) shows the check now fails. - R1, R3: a heredoc after a closed "$( )" keeps its reader; an escaped blank, semicolon or newline before a # is not a comment start. Every expected value of the D7, R1 and R3 commands was run under GNU bash 5.2.21 and dash with a stub curl (and a stub ssh that runs sh on its stdin, the remote login shell): both shells agreed on every count. At 7091a300: node suite 115 passed, 31 failed (5 linear, 26 parser); D7 9 commands, R1 5, R3 3 fail with 0 != 1 on all three carriers; exec_header_state raises ValueError on 5000 digits; shell_script flattens argv. The old D9 assertion passes when 7 of 7 stress inputs throw. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Quote Codex argv elements and bound the exec header exit code U1 pivot brief D6 (GPT-6 findings #11 and #12). shell_script joined an argv array with spaces, so a metacharacter inside one element became shell syntax that was never executed: ["echo", "qmd; rtk proxy qmd status"] counted one qmd call and one rtk proxy call. It now joins with shlex.join (Python 3 shlex.join, the inverse of shlex.split), so each element is one quoted word. The [shell, -lc, script] form (openai/codex rust-v0.157.1 codex-rs/core/src/shell.rs) stays the script itself, but only when the program is a shell: -c of `echo -c ...` is one of echo's arguments. exec_header_state passed the exit code text to int() whenever it was ASCII digits, and CPython raises ValueError above 4,300 digits (int max str digits, docs.python.org/3/library/stdtypes.html#int-max-str-digits), which stopped the scan on one malformed header. The code is an i32 written in decimal (at most 10 digits with its sign, codex-rs/core/src/tools/context.rs:534-540); a code of more than 9 digits is no native header and reads (False, "unknown"). The D6 tests of the previous commit pass: exec_header_state on 5000 digits, the shell_script cases, and the three Codex bridge cases (local shell call, code-mode item, `echo -c`). The existing Codex state tests still pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Scan unclosed (( in linear time: one paren table per text (D8, R9) U1 pivot brief D8 (GPT-6 finding #9; Claude review R9). Every unclosed "((" or "$((" made step() look ahead to the end of its line (indexOf the newline, then count parentheses), so n = 8000, 16000 and 32000 of "((" took 246, 1218 and 4888 ms at 7091a300 (four times per doubling), and openers() filtered its whole cut list once per heredoc operator. parenClose(s) now matches every "(" of a text with its ")" in one stack pass per line (bash(1) ARITHMETIC EVALUATION: (( )) and $(( )) count every ( and ) up to the end of the line, which is classic parenthesis matching), and step() reads its answer from the table. The last eight texts stay memoized so the scans that alternate between a text and its slices do not rebuild it; executedTrace clears the memo when a top-level analysis ends. openers() finds each operator's bounds in the ascending cut list by binary search instead of filter and find. Output is unchanged: executedText (both modes) and fetchKind of the base kernel and this one agree on all 150,000 checks (the 243 covering commands, the 10,000 heredoc fuzz strings, and two seeded 20,000-string token corpora with and without heredocs; 0 differences, 0 throws). Timings at n = 8000, 16000, 32000, 64000: "((" 3/6/13/27 ms, "$((" 4/9/19/40 ms, unclosed (( inside "$( " 5/8/18/38 ms, "(<<E" runs 8/14/30/64 ms, each within 2.5x per doubling and under 150 ms at 64000. The D8 test allows 5 ms of timer noise on the 2.5x bound (a linear scan showed 7.6 -> 19.4 ms once) and takes the best of five. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Read a # after an escaped blank or metacharacter as data (R3) Claude review R3 of 3cb7c4f6. In a "$( )" frame step() treated a # as a comment start whenever the previous character was a word break, ignoring whether that break was escaped. `x="$(echo a\ #b)"; curl ...` therefore read `#b)"; curl ...` as a comment: closeQuote and matchParen ran to the end of the command and the `; curl` fell inside double-quoted data (shell_fetch 0, fetchKind null, one unconfirmed mention). bash(1) COMMENTS: a word beginning with # starts a comment, and an escaped blank, `;`, `(` ... or newline (a line continuation) does not end a word (QUOTING: a backslash escapes the next character). escapedAt(s, k) counts the backslashes before s[k]; an odd count means it is escaped. It runs only for a # that follows a word break, and reads one run of backslashes per such #, so the scan stays linear. Verified with GNU bash 5.2.21 and dash and a stub curl: for each of the six R3 inputs (escaped blank, escaped semicolon, backslash-newline, an even backslash count before a real comment, a real comment inside "$( )", and a top-level escaped blank) both shells ran the stub curl once, so each is one executed fetch. R3 test passes on all three shell carriers. Differential of the previous commit's kernel and this one over 90,243 inputs (243 covering commands, 10,000 heredoc fuzz strings, and two seeded 40,000-string token corpora, with and without heredocs; executedText in both modes and fetchKind): 228 differing inputs, every one containing an escaped word break before a # (0 unexplained). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add failing-first R1 shapes and the --shell-parser refusal test Tests only. Extends the R1 cases of 62cb6935 with the shapes the fix has to cover beyond the reviewer's repro, each run under GNU bash 5.2.21 and dash with a stub curl (both shells agreed): - a quote inside the substitution: FOO="$(echo "a b")" bash <<'EOF' and the ssh variant, where the naive word split cuts the quoted word at the inner blank (curl ran once; with cat as the reader it ran 0 times); - a string that runs over lines before the operator: x="a\nb" bash <<'EOF' and the single-quoted variant, and a "$( )" that closes on a later line with text after its ")" (curl ran once each); - two closed "$( )" words, and controls that already read correctly at f83f8367 (a separator before the operator, a backquote span, a nested "$( )" with its own heredoc, cat as the reader). At f83f8367 the 11 executed shapes each fail on all three shell carriers (33 subtests: shell_fetch 0 != 1), the four controls pass. Also: an explicit --shell-parser that names a directory with no install exits 2 with the reason and no report (the --rtk-db and --exceptions convention), never a silent fallback to the default. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Bound each heredoc by the cuts of its own frame level (R1) Claude review R1 of 3cb7c4f6. openers() kept one cut list for every nesting level, so the ( and ) that a double-quoted "$( )" adds also bounded the heredoc operators outside it: in FOO="$(pwd)" bash <<'EOF' the operator's command started at the closing quote, its reader resolved to the empty string, and a body that bash reads as a script became data (shell_fetch 0, one unconfirmed mention, fetchKind null; real bash and dash run the stub curl once). step() now reports each cut with its frame (on.cut(i, frame): the "$(" frame that a ( opens and a ) closes, else the frame the unit sits in, undefined for the top level) and openers() keeps one ascending cut list per level; an operator is bounded by the cuts of the level on top of the stack, found by binary search. The cuts of a substitution bound the commands inside it and no longer the command that contains it. commandWords() reads a "$( )" or a backquoted span inside double quotes as one unit of its word (POSIX.1-2024 XCU 2.6.3: the substitution's text is tokenized on its own), so a quote or blank inside it does not split the word: FOO="$(echo "a b")" bash <<'EOF' names bash. R1 tests (62cb6935, 58502c60) pass on all three shell carriers, and the lane reading of FOO="$(pwd)" bash <<'EOF' with qmd in the body counts one qmd call. The shapes below are documented limits, pinned by their own test: a command that a quote or "$( )" carries over lines (x="$(cat <<'A' ... \n)" bash <<'B' and x="a\nb" bash <<'EOF') has its head on an earlier line, so this line alone gives the operator no reader and the body reads as data (fetch_mentions_unconfirmed 1, status incomplete: a possible fetch, never a lost one). A first attempt cut the line at the closing quote's word end; measured on the fuzz corpus it named the wrong owner as often as the right one (ssh host cat "a\nb" bash <<EOF), so it was dropped. Differential of f83f8367 and this commit over 90,243 inputs (243 covering commands, 10,000 heredoc fuzz strings, two seeded 40,000-string token corpora, without and with heredocs; executedText in both modes, fetchKind and the M4 confirmed and unconfirmed counts): 17 inputs differ, all with a quote, backquote or "$( )" before a heredoc operator (0 unexplained); no input lost mass (confirmed plus unconfirmed); 16 keep both counts, one moves a fetch from confirmed to unconfirmed. That one, bash -s -- "$(pwd)"bash <<EOF with an unquoted-delimiter body, now names bash as the reader (correct) and reads the body as source, where a substitution inside single quotes is data: the "$( )"-free twin reads C=0 U=2 at 7091a300 as well (real bash runs curl once: the outer shell expands an unquoted heredoc body before the reader sees it, a limit that predates R1 and is unchanged). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Read a double-quoted run string in two views: outer expansion (D7) U1 pivot D7, GPT-6 finding #8. N1 read a double-quoted string that a shell runs only as that shell's input, so a substitution the OUTER shell runs while it expands the word was lost whenever the inner shell reads it as data: a quoted heredoc, single quotes, a comment. bash -c "cat <<'EOF'\n$(curl https://example.org)\nEOF" went from fetchKind 'fetch' (cf3fb72e) to null with shell_fetch 0. bash 5.2.21 and dash run the stub curl once: the expanding shell runs each unescaped "$( )" and backquote before the shell it starts reads anything (POSIX.1-2024 XCU 2.2.3 Double-Quotes, 2.6.3 Command Substitution). Two views now: outer bodies first, each as commands of their own, then the inner shell's read of the string with each outer span replaced by a placeholder word (its output is unknown), so a substitution both views could see counts once. A backslash-escaped \$( ) or backquote is no outer span and stays the inner shell's (bash -c "echo \$(curl u)" runs curl once, in the inner shell). Single-quoted run strings have no outer expansion and are unchanged. outerSpans() is the one scanner of a double-quoted string's substitutions (escape pairs skipped, "$((" arithmetic not a span but the substitutions inside found, NESTING_LIMIT and unterminated spans as before); quotedData() now consumes it, so the data view and the run view cannot disagree. The ssh remote ranges (marks) cover the literal text between spans, not the local substitutions. D7 tests (62cb6935) pass on all three carriers: 13 executed, 5 inner and 2 twice-run commands, 2 data commands; the qmd lane test counts one call. Evidence, all against GNU bash 5.2.21 and dash with stub curl/wget/ssh (ssh runs its arguments with sh -c, or reads stdin as the remote login shell), counting stub calls against the kernel's confirmed count (shell_fetch + loopback): - compositional oracle, 360 commands (4 double-quoted run carriers x 10 contexts x 4 atoms, two-atom strings, single-quoted carriers, heredoc readers x delimiters x bodies): base 7091a300 301 exact, 59 under, 0 over; 43b9d947 329, 31, 0; this commit 353, 7, 0. The 7 left are one class, a shell-read heredoc with an unquoted delimiter whose body has a substitution inside single quotes (the outer shell expands the body first), next commit. - random valid-bash fuzz, 40,000 candidates, 7,883 accepted by bash -n and dash -n with equal stub-call counts under both (361 with a fetch): base, f83f8367, 43b9d947 and this commit agree on the same 7,789 (98.81%) and the same 94 disagreements (12 over: word glue and a trailing backslash; 82 under: remote ssh commands and glued words), all present at 7091a300. - differential of 43b9d947 and this commit over the covering commands and three fuzz corpora (90,243 inputs): 13 inputs lose confirmed mass, every one invalid bash in at least one shell; of the 24 differing inputs without HTTP-library or gh tokens, 21 are invalid, 2 valid inputs move closer to real bash and 1 (xcat &&bash -c "'$(curl u)'") counts a substitution that a failing && never reaches, which no static reading models. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add failing-first tests: an unquoted heredoc body is expanded first Tests only. D7 for a heredoc body (U1 pivot D7, same defect class as GPT-6 #8): with an unquoted delimiter the shell that reads the command line expands the body before the shell that reads the heredoc sees it, so each unescaped substitution runs in the outer shell whatever its quotes or a comment say (bash(1) Here Documents; POSIX.1-2024 XCU 2.7.4), quotes are literal, and a backslash escapes only $ ` \ and a newline. 20 commands, each run under GNU bash 5.2.21 and dash with stub curl and ssh: curl ran once for 17 (a single-quoted substitution, a comment, nested quotes, a substitution over lines, a <<- body with tab indents, arithmetic, an ssh reader, a run string in the body, and controls that already read correctly), twice for one (an outer and an inner substitution), and never for two (a quoted delimiter passes the body as is). At 67a4acbb 9 of the 17 fail on all three shell carriers (shell_fetch 0 != 1, one unconfirmed mention: bash <<EOF with echo '$(curl u)', the FOO="$(pwd)" and ssh readers, a comment, nested quotes, a multi-line substitution, <<-, and arithmetic); the rest and the data commands pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Expand an unquoted heredoc body a shell reads before reading it The heredoc form of D7 (GPT-6 #8: the outer shell runs what it expands before the inner shell sees the text). With an unquoted delimiter the shell that reads the command line expands the body first: each unescaped $( ) and backquote runs there whatever its quotes or a comment say, quotes are literal in a body, and a backslash escapes only $ ` \ and a newline (bash(1) Here Documents; POSIX.1-2024 XCU 2.7.4). resolveHeredocs() analyzed a body that a shell (or in inlineHttp mode an interpreter) reads only as that reader's source, so bash <<EOF with echo '$(curl u)' read as data although real bash and dash run curl once, and so did a substitution in a comment, nested quotes inside it, one over lines, arithmetic, and a <<- body: the same 9 shapes as the failing-first commit b6f3b454. R1 made the FOO="$(pwd)" bash <<EOF form read this way too, so this also removes the accident that had hidden the limit there. The body now has two views, sharing D7's outerSpans/withoutSpans/outerBody: the outer spans first, as commands of their own (heredocs inside them resolved there), then the reader's source with each span replaced by the placeholder word and its own escapes applied (heredocText). A quoted delimiter is unchanged (nothing is expanded), and so is a data heredoc such as cat <<EOF, which keeps the substitutions its regular expression finds. The ssh remote ranges cover the body's literal text, not its local substitutions. The heredoc test of b6f3b454 passes on all three carriers (17 once, 1 twice, 2 data commands); tests.test_token_measurement passes (59). Evidence, real bash 5.2.21 and dash with stub curl/wget/ssh, kernel confirmed count against stub calls: - compositional oracle, 441 commands (D7's 360 plus escaped, comment and nested-quote bodies): base 7091a300 368 exact, 73 under; 67a4acbb 420, 21; this commit 441 of 441 exact, 0 under, 0 over. - grammar fuzz, 6,000 valid nested commands (statements, sequences, substitutions, backquotes, single and double quotes, run strings, heredocs with unique delimiters) that bash and dash ran with equal stub-call counts (3,049 with a fetch, 1,597 with a run string, 1,471 with a heredoc): base 5,788 exact (96.47%), 212 under, 0 over; 67a4acbb 5,970 (99.50%), 30 under, 0 over; this commit 5,975 (99.58%), 25 under, 0 over. All 25 are one older gap, a run string right after a backquote (echo `bash -c "curl u"`), whose text is invisible to the raw detector as well; fixed next. - random valid-bash fuzz, two seeds of 40,000 candidates (7,883 and 7,838 valid, 361 and 374 with a fetch): base, 67a4acbb and this commit agree on every input. - differential of 67a4acbb and this commit over 90,243 inputs: 924 differ, 20 with a lower confirmed count, of which 19 are invalid bash or valid only with a heredoc-at-end-of-file warning, and the one clean input (a backquote span that holds bash <<EOF) moves from 3 to 0 confirmed where real bash and dash run curl 0 times. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add failing-first tests: a run string after an opening backquote Tests only. RUN_QUOTED anchors a shell, eval or ssh word at the text start, a blank, ; & | or (, but not at a backquote, so bash -c "curl u" inside `...` read as double-quoted data. The fetch was lost, and the raw detector, which anchors curl itself and not the quote, counted no possible fetch either (C=0 U=0). Found by the grammar fuzz of 2a33ef90: all 25 commands left under-counted there were this one shape. 8 commands run under GNU bash 5.2.21 and dash with stub curl and wget: the stub ran once for each (bash -c and sh -c with double or single quotes, eval, an ssh string, a backquote inside a single-quoted run string, an escaped backquote in a double-quoted one, and two controls that already read correctly: a blank after the backquote and $( ) in its place) and never for echo `echo "curl u"` (data). At 2a33ef90 the 6 backquote-adjacent shapes fail on all three shell carriers (shell_fetch 0 != 1, no unconfirmed mention: 19 failures with fetchKind). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Anchor a run string at an opening backquote too RUN_QUOTED and RUN_HTTP_CODE anchored a shell, eval, ssh or interpreter word at the text start, a blank, ; & | or (, but not at the backquote that opens a `...` substitution, so bash -c "curl u" inside backquotes read as double-quoted data: the fetch was lost and no possible fetch was counted either (C=0 U=0). bash(1) Command Substitution: a `...` body is shell text with its own command positions, as a $( ) body is; the anchor class now holds the backquote (one character in each regular expression; no new alternative, so no new backtracking). The 6 backquote shapes of the failing-first commit (66550644) pass on all three carriers; tests.test_token_measurement passes (60). Evidence, real bash 5.2.21 and dash with stub curl/wget/ssh, kernel confirmed count against stub calls: the 441-command oracle stays 441 exact; the grammar fuzz that found the gap is now exact on every input, 6,000 of 6,000 (3,049 with a fetch, 1,597 with a run string, 1,471 with a heredoc; 2a33ef90 5,975 exact, 7091a300 5,788, 96.47%) and, on a second seed, 11,999 of 11,999 (base 11,608, 96.74%, 391 under; 0 over throughout). Differential of 2a33ef90 and this commit over 90,243 inputs: 216 differ, 1 with a lower confirmed count, an unterminated string (invalid bash). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add a pinned, verified tree-sitter-bash loader that fails closed (D1) U1 pivot D1. The CLI-lane reading is to run on tree-sitter-bash, the grammar OpenAI Codex uses for the same job (openai/codex rust-v0.157.1 36650394 codex-rs/shell-command/src/bash.rs; tree-sitter-bash = "0.25" at codex-rs/Cargo.toml:522, both read at the tag). This commit adds the seam and the gate; the AST reading itself is the next stage. shell-parser.pin.json pins the install: web-tree-sitter 0.27.0 and tree-sitter-bash 0.25.1 with their npm integrity values (checked against the registry's dist.integrity), upstream tags and commits (v0.27.0 6070dbfe = npm gitHead; v0.25.1 names a06c2e44 while the package was published from 80132668, five minutes earlier, recorded as a note), the sha256 of the six files the loader reads, and the install command (npm install --prefix <dir> --ignore-scripts --no-audit --no-fund --save-exact). loadShellParser(dir) resolves the directory as the argument (the new --shell-parser flag), then CHILD_USAGE_SHELL_PARSER, then the ecosystem tools directory under HOME, reads each pinned file once, compares its sha256 and the lockfile's version and integrity values with the pin, and executes only those bytes: the runtime module is imported from a private 0600 copy of the hashed bytes and the two wasm files are handed over as bytes, so the code that was hashed is the code that runs. Results carry no path: { ok, versions, wasm_sha256 } or { ok: false, reason } with not_installed (directory, a pinned file or the lockfile missing), hash_mismatch, load_error (an unreadable file, the pin file, init) and not_loaded (this process never awaited the loader, so a forgotten await is not mistaken for a missing install). A failed load leaves no parser, whatever an earlier call did; a successful one reuses the initialized runtime. withShellTree() frees every tree in a finally. Fail closed: commandInvocations() returns null without a parser and measurement reports cli_lanes { status: parser_unavailable, reason }, keeps the prefix rule for the rtk proxy carrier (proxy.rule prefix_fallback, invocations and in_ctx_code null) and never falls back to the text scanners for lane counting; M4 stays text based. A measured cli_lanes carries status measured and the parser record (versions and the two wasm sha256 values); aggregates report measured, incomplete (mixed actors, with the counts), parser_unavailable or not_measured, and sum proxy counters null-aware. The CLI awaits the loader; a --shell-parser that cannot be honored exits 2 with the reason (like --rtk-db), any other missing install only leaves cli_lanes unavailable. The Codex bridge awaits the loader too. Interim, until the AST reading lands: with a verified parser loaded, commandInvocations() still runs the scanner-based lane layer of 3cb7c4f6, so every existing lane fixture reads as before; the gate, the status fields and the seam (withShellTree) are final. Tests (62cb6935, 58502c60 first, run failing at 7091a300): the pin's contents; not_installed for an absent directory, a missing pinned file and a missing lockfile; hash_mismatch for one flipped byte in the bash wasm, a modified web-tree-sitter.js that is never imported, a modified package.json, another integrity value and another version in the lockfile; load_error for an unreadable file; directory order; --shell-parser; a good install loading again after failures; unavailable and mixed aggregates; the bridge in both states. The lane fixtures load the parser first and skip with a message on a host without an install. tests: node suite 149 passed; tests.test_token_measurement, test_skill_usage and test_child_usage_suite 175 passed. D9's stress check reports the TypeError the lane checks threw before the parser was loaded (threw: TypeError x8), which the old assertion would have passed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add failing-first timing tests for quote-heavy scripts (D8) Tests only. D8 asks the M4 text scanners for linear time; the unclosed "((" input of GPT-6 #9 is fixed (5e814331), but profiling a realistic 346 KB script (4,000 lines of echo "step N: $(date)" >> log; qmd search "term N" | head) at e5238f39 showed the same quadratic shape from another term: 51% of the time in the run-string regular expression and 33% in scanQuotes, both from one read of the whole text built so far per quote (the regex, and the string flattening that indexing a concatenation rope forces). A 42 KB script takes 47 ms, 84 KB 108 ms, 172 KB 467 ms, 346 KB 2,078 ms and 694 KB 8,236 ms a scan, and the kernel scans each shell call several times. Five doubling checks (n = 8000..64000, best of five, at most 2.5x per doubling plus 5 ms of noise, 64,000 under 1.5 s): a run of quoted words, a run of double-quoted substitutions in inlineHttp mode, a run of `bash -c "x"` strings, a run of words with a # inside, and a long script of echo, substitution and pipe lines through fetchKind. At e5238f39: 61, 230, 902, 5,861 ms; 168, 651, 3,728 ms; 991, 4,486 ms; 9, 32, 120, 459 ms; 89, 392, 1,930 ms (the series end where a run passes 1.5 s). All five fail. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Read only the tail of the built text at each quote (D8: linear scripts) scanQuotes() tested RUN_QUOTED (and in inlineHttp mode RUN_HTTP_CODE) against the whole text built so far at every quote. Reading out.s for that flattens the string concatenation and scans the full prefix each time, so a script with n quoted words cost n squared. CPU profile of a 346 KB script (4,000 lines of echo "step N: $(date)" >> log; qmd search "term N" | head) at e5238f39: 51% in the run-string regular expression, 33% in scanQuotes itself (the flattening), 10% in GC. The five timing tests of 9da57414 failed on it. scanQuotes now keeps the end of its output in `tail`, cut back to 512 characters whenever it passes 1,024, updated at every write (put and putTrace; quotedData builds into its own buffer first), and reads only that: the # test uses the last character of the tail, the emptiness test out.p.length. Once a cut has been made the two detectors run without their start-of-text alternative (a cut is no start of text). Limit, stated in the code: a run keyword more than 512 characters before its quote, after a text that has grown past 1,024, is missed and the string reads as data. Only a single ssh option word that long can put it there (ssh -oProxyCommand=<1500 characters> host "curl u": fetch before, null after; 100, 400, 520 and 700 characters read the same), since the words between a shell, eval or ssh and its string are short options and one host. Timings (best of five, ms), n = 8000, 16000, 32000, 64000: quoted words 61, 230, 902, 5,861 -> 8, 15, 31, 69; double-quoted substitutions (inlineHttp) 168, 651, 3,728 -> 15, 31, 67, 140; run strings 991, 4,486 -> 38, 83, 173, 358; words with # 9, 32, 120, 459 -> 3, 6, 14, 37; a long echo/substitution/pipe script through fetchKind 89, 392, 1,930 -> 18, 34, 71, 154. Realistic scripts, executedText: 84 KB 108 -> 37 ms, 346 KB 2,078 -> 100 ms, 694 KB 8,236 -> 205 ms, 1.4 MB 393 ms (2x per doubling); the node suite runs in 12 s instead of 41 s. Output is unchanged: executedText (both modes) and fetchKind of e5238f39 and this commit agree on all 1,084 shell texts of this repository (822 .sh files and fenced shell blocks of .md files, 1.66 MB, largest 82 KB, 83 over 4,000 characters), on the 243 covering commands and the three fuzz corpora (90,000 inputs), and on 600 synthetic long scripts (0 differences, 0 throws); the 441-command oracle stays 441 exact, and the 6,000-command grammar fuzz stays 6,000 exact (real bash 5.2.21 and dash with stub curl). Also fixed while rewriting the call: the ssh remote offset test listed the separators before RUN_QUOTED's keyword but not the backquote that 296c4f1d added, so in echo `ssh host "qmd get a"` the qmd call read as local; it now reads qmd/qmd remote (fixture added; the same input read local at e5238f39). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Flip one real byte in each pinned file in the loader tests The loader tests changed the bash grammar wasm by reading it as UTF-8 and appending a NUL, which rewrites the whole binary: the test passed because the file differed, not because one byte did. It now reads a Buffer and flips bit 0 of the middle byte in place (same length, so only the hash can tell), and does the same for each of the six pinned files in turn: every one gives hash_mismatch, leaves no parser (commandInvocations returns null, the scanners do not step in) and runs no unverified code (the modified web-tree-sitter.js sets a global if it is ever executed; it is not). The missing-file and missing-lockfile cases now also assert that no parser stays loaded. Node suite: 160 passed (149 before). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add a failing-first test: a run keyword past the tail window Tests only. The brief's D8 says a work budget "marks the command unclassifiable for M4 instead of throwing". 1d77c34c bounds scanQuotes to the last 512 characters of its output, but an ssh option word long enough to push `ssh` out of the window (in a text past 1,024 characters) drops the run string silently: the fetch in ssh -oProxyCommand=<1500 characters> host "curl u" is read as data with no possible fetch counted either (shell_fetch 0, unclassifiable 0, unconfirmed 0), which a budget must not do. The test: option words of 100, 400 and 700 characters read as before (the fetch confirmed, unclassifiable 0); 1500 characters counts one unclassifiable operation (remote_fetches 1, no confirmed shell fetch); the count is once per command and only for the command that held the keyword (two later quoted commands add nothing); a long printf with hundreds of words and quotes but no run keyword adds none. At 1d77c34c the two 1500-character cases fail on all three shell carriers (unclassifiable 0 != 1: 6 failures); the 100, 400 and 700 cases and the printf control pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Count a run string the tail window lost track of as unclassifiable The brief's D8 fallback for input past a work budget is "marks the command unclassifiable for M4", not a silent miss. 1d77c34c bounds scanQuotes to the last 512 characters of its output; an ssh option word long enough to push `ssh` out of the window (in a text past 1,024 characters) dropped the run string with no trace: ssh -oProxyCommand=<1500 characters> host "curl u" read as shell_fetch 0, unclassifiable 0, unconfirmed 0. scanQuotes now notes, outside quotes, when a shell, eval, ssh or su word (and its blank) has been written since the last command separator (a last-letter pre-check keeps it off the hot path). When a cut has left the tail without that word and without a separator, the next quote that does not read as a run string is counted once in windowMisses, which countFetches adds to unclassifiable (an operation of unknown kind: remote_fetches 1, no confirmed shell fetch). The keyword is noted at write time because the discarded text cannot say whether a word was quoted: a first version that looked for the word in the discarded text also fired on 4 real scripts of this repository (adoption/bootstrap-linux.sh, bootstrap-macos.sh, launchd-agents.sh and a memory-stack README block), where separators inside quoted data are blanked and a word such as sh sat in a long quoted list. The test of 0b2b5778 passes on all three carriers (100, 400 and 700 characters read as before; 1500 counts one unclassifiable, once for the command, and adds nothing for later commands or for a long printf with no run keyword). The counter is zero on every corpus: complete m4 results of the pre-window kernel (e5238f39) and this one are identical on the 243 covering commands, 10,000 heredoc fuzz strings, two 40,000-string token corpora and all 1,084 shell texts of this repository (822 .sh files and fenced shell blocks, 1.66 MB), 2 tools each; the 600 synthetic long scripts agree; the 441-command oracle is 441 exact and the grammar fuzz 11,999 of 11,999 exact against real bash and dash. Realistic scripts, executedText: 84 KB 50 ms, 346 KB 125 ms, 694 KB 285 ms (2,078 and 8,236 ms at e5238f39). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Keep 150 ms for the brief input only; loosen the extra timing shapes The brief's D8 requires at most 2.5x per doubling and under 150 ms at n = 64,000 for '(('.repeat(n) + 'qmd'. The timing tests added alongside it (a run of $((, the same "$( ((, a run of ( << E, and the quote-heavy shapes) had inherited that absolute bound. Three of them run 64-80 ms at 64,000 against 150 ms on this host, thin headroom for a slower CI runner. Only the brief's input keeps 150 ms (it runs about 30 ms); every other shape keeps the 2.5x ratio (plus 5 ms of timer noise, best of five) and takes an absolute bound of 1.5 s at 64,000, which is still far below the quadratic behavior they replaced (the same shapes take 1.9 to 10 s and fail the ratio at 7091a300). Run against the base kernel (7091a300) with this test file, all ten timing shapes fail (for example (( 313, 1,219, 5,109 ms; quoted words 62, 239, 928, 6,211 ms), and against this head all ten pass (for example (( 3, 7, 17, 32 ms; quoted words 8, 15, 32, 71 ms; run strings 42, 95, 205, 416 ms). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Retry the doubling timing rounds so one pause cannot fail a linear scan A direct run of test-child-usage.mjs at da2d578d failed one assertion, "a run of unclosed (( at n = 8000, 16000, 32000, 64000 takes [4, 7, 27, 31] ms": the 32,000 reading was 27 ms where the scan takes about 15, so the ratio to the 7 ms before it (3.9x) broke the 2.5x (+5 ms) bound. The same suite had passed inside the Python run seconds earlier. A flaky timing check is a CI hazard, so the doubling measurement now takes the best of five per size and, when the ratio still fails, up to two more rounds and the elementwise minimum of all of them: a pause cannot fail a linear scan, while a quadratic one fails every round. Checks on the final file: six consecutive runs and two runs beside six busy CPU loops, all 160 passed; against the base kernel 7091a300 all ten timing shapes still fail (the retry does not hide quadratic behavior). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add failing-first tests for the AST command-position layer (D10, D11) The GPT-6 review of the scanner-based lane reading (3cb7c4f6) found ten command-position defects (#1-#7, #10 here; #8, #9, #11-#13 were repaired earlier) and the Claude review five more (R4-R7). This commit holds their probes before any repair, so each one fails for its defect. - tests/test_token_measurement.py: one test per finding, with the GPT-6 inputs verbatim, asserting what the finding states (lane calls, proxy calls, exclusions, unresolved programs, downstream keys, call states), not a display string. Also parse_errors (D2), the closed name vocabulary (D5) and mcporter's help/version tokens (R6: checked against openclaw/mcporter@93e0916c src/cli.ts:137-145 and flag-utils.ts:34-41, the code already matches; the test pins it). - test-child-usage.mjs: a program name is emitted only for a lane executable or a name the reading interprets (GPT-6 #10). - tests/test_command_position_oracle.py (D10): a deterministic generator and a hand-written probe list run under REAL bash 5.2 with env -i, a PATH of logging stubs, a temporary HOME and cwd and a 5 s limit; the multiset of lanes that ran must equal the lanes commandInvocations reads. Against the scanner layer: 38 of 99 probes and 59 of 457 generated commands disagree with the run. Every expected value is the run of real bash and dash under stub executables (the oracle covers each input), not what this kernel says. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Read shell text with the tree-sitter-bash AST for CLI lanes (D2-D4) Replaces the scanner-based command-position layer (simpleCommands, resolveInvocation, analyzeScript) with a walk of the tree-sitter-bash tree, the grammar openai/codex rust-v0.157.1 codex-rs/shell-command src/bash.rs uses for the same job. The option tables, runner maps and mcporter grammar are unchanged and now operate on word values. - D2: every `command` node counts wherever it sits (substitutions inside strings, arithmetic, arrays and unquoted heredoc bodies, subshells, compound and control statements, function bodies); array elements, case patterns, [[ ]] operands, function names and the words of any non-lane program never do. ERROR nodes and what is under them are skipped and the call counts once in cli_lanes.parse_errors. - D3: wordValue() returns a literal or null: unquoted escapes, '...', the double-quote rule (a backslash goes only before $ ` " \ and newline), $'...' decoded as bash does, concatenations; any expansion, glob, brace expansion is unknown. A tilde prefix keeps the word known for program identity (only the directory changes: ~30 lane invocations by ~/path in 136,361 real commands) but not as a script. - D4: bash|sh|dash|zsh|ksh -c reads the first operand after the options (-n, -D and -o noexec run nothing; `--`, `-` end the options); a shell with no -c reads standard input, so a heredoc or here-string attached to the last command of a list, pipeline or negation, through env, timeout, nice, nohup, stdbuf, rtk proxy (not xargs, which consumes it), has its body read; eval and ssh join their words as the callee does; rtk proxy resolves its program with exec semantics (command and exec run nothing, time is unresolved, an expansion in its one argument is unresolved). - Backquotes and outer substitutions of an unquoted heredoc body and of a double-quoted script are read from the raw text with the kept M4 helpers (tree-sitter-bash leaves backquotes in heredoc_content and mis-reads some bodies); a script with an expansion is read with a placeholder. - The grammar folds the next line into a command in some shapes (an unquoted == before ;, &&, | or a newline; a trailing blank before a newline; `<<'EOF' 2>&1 | tail`): 732 of 136,361 real commands with no error and 163 with one. A separator ERROR or an unescaped newline inside one command's words starts a new command. - simple counts assignment-only statements, declarations and each loop, if and case as well as commands (POSIX.1-2024 XCU 2.9.1, 2.9.4), so the ambiguity of existing fixtures is unchanged. Fixtures: EMPTY_CLI gains parse_errors (D2), and `ssh host bash -s <<EOF` now also reads the remote bash as a command of its own (ssh(1): the words after the destination run remotely). Every other existing lane fixture passes unchanged. Against real bash 5.2: the oracle agrees on 457 of 457 generated commands (581 lane runs) and 99 of 99 probes. Over 136,361 distinct real commands the old and new readings agree on 136,167; the differences are old false positives, unknown wrappers (setsid, bwrap) and grammar defects. Still failing here by design, fixed next: D5 vocabulary (5 tests) and R7 states (7 tests); the node suite fails only on the D5 program-name check. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Re-expect the closed name vocabulary in the lane fixtures (D5) U1 pivot D5 and GPT-6 #10: commandInvocations returns `program` only for a lane executable or a name this reading interprets itself (a wrapper, a shell, eval, ssh, rtk), and an mcporter downstream key is emitted only for the stack's own servers; every other name-shaped string (a host, an id, a package, `linear`) reads null or (other). The old expectations that showed `-/echo`, `-/git`, `-/pytest` or `@linear` are wrong under D5, so they are changed here, before the code: 7 node checks and 3 Python tests fail against the walker of 1d35df12 with the observed values recorded in the log (`program` still holds "my-private-host.example", the downstream key still holds "linear" and "call_PRIVATE"). The selector-form coverage the `linear` cases gave is kept with servers of the stack (`socraticode`, `serena`, `jcodemunch`, `ai-memory`, `qmd`, `headroom`, `context-mode`, `codebase-memory`), read by name in every form. Sources of the closed set: manifests/stack.json:280 (codebase-memory), :407 (context-mode), :629 and :966 (socraticode); the rest is the coordinator's list in the pivot brief. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Emit only fixed names: a closed program set and mcporter server set (D5) GPT-6 #10: `program` carried any name-shaped basename (a host, an id, a package) and an mcporter downstream key any name that passed a character class, so the no-id, no-host output contract held only by luck. - `program` is set only for a lane executable or a name the reading interprets itself (the wrappers, the shells, eval, ssh, rtk); every other program reads null. - An mcporter downstream key is one of codebase-memory, context-mode, jcodemunch, serena, socraticode, qmd, headroom or ai-memory (the stack's servers: manifests/stack.json:280, :407, :629, :966 and the coordinator's list), else (other), (http), (stdio) or (unresolved). The tests of the previous commit now pass (node 162 of 162; the Python name-vocabulary test and the three re-expected fixtures). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Expect an interrupted counter in lane and downstream rows (R7) Claude review R7: a Claude Code result whose toolUseResult.interrupted is true read as succeeded. The command started and was cut short, so it is a state of its own. The shared fixture helpers (lane_row, downstream_row and the Codex cli_lane_row and downstream row) now carry `interrupted: 0`; the lane and downstream rows of the kernel do not have the key yet, so the tests that compare whole rows fail (see the log of this run) until the next commit adds the counter and the state. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Read not-executed calls where the client records them; add interrupted (R7) Claude review R7: the marker "The user doesn't want to proceed" was matched against the row's toolUseResult, where it was never observed; it opens the result CONTENT. Observed on this host's Claude Code transcripts (122,648 Bash results in 4,355 files, count-only): the rejection content (25 rows, toolUseResult "User rejected tool use"), a row-level toolDenialKind on every client denial (permission-rule 750, user-rejected 46, cancelled 1, automode-unavailable 1), the classifier text "The server-side auto mode classifier gave no verdict" (its own text says the check failed, not the action), and 209 rows of a host hook's refusal text ("This agent is isolated") that carry no toolDenialKind and stay failed (open: its meaning is the hook's, not the client's). - callState: not_executed when the row has a toolDenialKind, or the content opens with <tool_use_error>, "The user doesn't want to proceed" or the classifier text, or toolUseResult names a hook denial, a permission denial or a rejection. - toolUseResult.interrupted === true is its own state, `interrupted` (none of 25,506 observed object results, so it is a synthetic fixture), with a counter in lane and downstream rows; it no longer reads succeeded. The R7 test of the first commit and the fixtures of the previous commit pass (measurement and Codex modules: 188 tests). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Remove the lane-only marks plumbing from the M4 text machinery The scanner-based lane reading asked executedTrace for `marks` (the raw range of each ssh body and the offset of each command substitution of a data heredoc body). The AST walker reads both from the tree and the raw text, so nothing passes marks any more: the parameter and every branch that wrote to it (resolveHeredocs, scanQuotes, outerBody, quotedData) and rawRange are removed. The M4 readings are unchanged: the complete m4 object of measureTranscript (Bash and ctx shell carriers), executedText in both modes and fetchKind are identical between the kernel before the walker (f1ed98ac) and this one over 136,361 distinct real commands, the 243 covering commands, and 2 x 40,000 fuzz inputs (0 differences, 0 exceptions). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Test the recovery of commands the grammar joined to the previous one The walker of 1d35df12 starts a new command at a separator ERROR or an unescaped newline inside one command's words, because tree-sitter-bash 0.25.1 reads what follows a command as its arguments in these shapes (measured on this host's transcripts: 732 of 136,361 distinct real commands with no error and 163 with one): an unquoted == or =~ before ;, &&, | or a newline, a redirection after a here-document operator followed by | or ;, and a multi-line shape found in the real corpus. This commit adds the tests that pin it: - tests/test_command_position_oracle.py: 12 recovery probes whose expected lanes are what REAL bash 5.2 ran (12 of 12 agree; 10 carry a parse error, 2 none). - tests/test_token_measurement.py: the same shapes through measureTranscript, with parse_errors counted once for the calls whose tree has an ERROR, and two controls that are not boundaries (a backslash-newline continuation and a comment). Mutation control (recovery disabled in a scratch copy of the kernel): all 12 oracle probes fail (real bash ran the lane, the kernel read none or one fewer, e.g. `echo ==; qmd get a` ran qmd, read []) and 9 of the 12 measurement cases fail with the observed values ({} != {'qmd': 1}). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add failing-first tests: a line that begins with a backslash Found by the 14,461-command run of the oracle generator over more seeds against real bash: a line that begins with a backslash (`\ls`, the alias bypass; `\markitdown a.json`) reaches tree-sitter-bash 0.25.1 as a word that begins with the newline, so the line joins the command before it, and when it is the first line of a here-document body the grammar leaves the body node without it and adds its words to the operator's arguments (which hid that the owner is a shell). A here-string that precedes such a line was fed to the last command of the joined node instead of its own. Five shapes are added to the oracle's recovery probes and to the measurement recovery test; real bash ran every lane in them. Against the kernel of 907ac35f the two tests fail with the observed values (recorded in the log of this run), e.g. `{ timeout 5 repomix` + newline + `\markitdown --flag; }` read {'repomix': 1} where bash ran both, and the shell heredoc whose first body line starts with a backslash read {}. Also hardens the oracle generator: it drops a text bash rejects (`bash -n`), keeps a function definition and its call in one group, does not put `!` or a heredoc on the right of a pipe (`x | ! y` is not POSIX; a reader that ignores its pipe kills the writer with SIGPIPE), and writes data heredocs to /dev/null. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Read a line that begins with a backslash as a command of its own Repairs the shapes of the previous commit's tests: - commandSegments starts a new command at a word that begins on a new line (the grammar puts the newline of a `\cmd` line at the start of its word) and drops that newline from the word's value. - A here-document operator's arguments are only those on its own l…
seathatflowsinourveins
added a commit
that referenced
this pull request
Sep 30, 2026
… E2E (call states, M15, ledgers, D7, joins) (#528) * Add failing-first tests for hook blocks, loaded servers, JSON shapes Failing-first fixtures for PR-A items 1, 5 and 4 (U2 design sections 1-3, with the review-u2 findings that cite them), in the shapes a count-only scan of this host's 2.1.283/2.1.284 transcripts found. Item 1: inserted stays the row count M12 reads (E2E README M12 row); new inserted_blocks, blocks_by_event, claimed_by_event, plain-stdout claims only on UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch (code.claude.com/docs/en/hooks, "Exit code 0", fetched 2026-09-29), other hook_* rows, the sibling-row and sibling-child negatives, the M12 positive-control key, and a fold that keeps an over-long <Event>:mcp__<server>__<tool> hook name at server level. Item 5: loaded references per server and actors per server (RUNBOOK 'Loaded, never called'). Item 4: JSON and uniform-tabular counts per carrier after the ctx_execute(_file) code echo (mksglu/context-mode v1.0.169 src/server.ts:1511-1527) and the Read cat -n numbers, unknown counted, never 0 (TOON v4.1.1 packages/toon/README.md:199), and the section-1 per-actor figures the scope extract lists. At fe9f511b: by_hook has (other) for the long MCP name, the new keys are absent (inserted_blocks, loaded, large_json, shell_web: None), the node check fails 1 of 176; every control passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count hook blocks and claims by event, loaded servers and JSON shapes Implements PR-A items 1, 5 and 4 (U2 design sections 1-3) with the review-u2 findings that cite them; makes 06826ff3's tests pass. Item 1: hook_context adds inserted_blocks, blocks_by_event, claimed_by_event, claimed_plain_stdout (only UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch, and only stdout that does not both start with { and end with }: code.claude.com/docs/ en/hooks "Exit code 0"), other_hook_rows and hook_names_folded; an over-long <Event>:mcp__<server>__<tool> name keeps a validated <Event>:mcp__<server> key instead of '(other)'. inserted, claimed, with_marker and by_event keep their #432 meaning. The aggregate adds actors_with_insertion. Item 5: measurement.loaded (references per server, called or not); the aggregate adds loaded, loaded_actors and loaded_not_called_actors. Item 4: every sizes object counts large_json, large_uniform_keys, large_uniform_flat and large_shape_unknown. The payload is read after the carrier's wrapper: the ctx_execute(_file) code echo, rebuilt by the exported ctxEcho (mksglu/context-mode v1.0.169, 442f1eb6, src/server.ts:1511-1527), and the Read cat -n line numbers, both with linear scans; what cannot be read is unknown. uniform_flat follows TOON v4.1.1 packages/toon/README.md:199. The aggregate adds the section-1 per-actor figures: shell_web_large_results_per_actor, actors_with_mcp_call, actors_with_non_ctx_mcp_call (per-actor mcp_attempted) and actors_with_ctx_results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for advisor iterations and cache tier fields Failing-first fixtures for binding decision B9 (per-child usage reads usage.iterations[]: advisor_iterations apart, totals including them, usage.complete false when iterations cannot be read) and U2 correction 1 (cache_creation_5m/1h, speed, service_tier, inference_geo per message and per advisor iteration, null when absent, never 0). Shapes follow a count-only scan of this host's transcripts and the beta Messages API reference (platform.claude.com/docs/en/api/beta/messages/ create, BetaIterationsUsage), the advisor tool doc ("Usage and billing") and the refusals-and-fallback doc ("Billing and rate limits"), fetched 2026-09-29. Cases: tiers both nonzero, combined counter only (split null), fast speed, us geo, an advisor iteration with its own model and split, a multi-iteration row whose top-level cache_creation object is the first iteration's split (3,706 of 3,706 such rows here), streamed rows, adjacent windows, an empty list, and the reasons usage is incomplete: top level unequal to the message entries, fallback, compaction, a non-object or non-numeric entry, a successful advisor result without its entry, an advisor call without a result, an advisor entry without a model. Run-mode control: the run stays complete, exit 0. At 0eb8771c: the new fields are absent, every unreadable case reads complete true, the aggregate reads (True, 503) for (False, 503); node 177/179 with the control passing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read usage iterations: advisor apart, tier fields, split per entry Implements binding decision B9 and U2 correction 1 in transcriptUsage (measurement.usage: sweep actors[] and run-mode children[] and superseded_attempts[] lanes.measurement); makes 2bf419de's tests pass. usage.iterations[] (beta Messages API BetaIterationsUsage; advisor tool doc "Usage and billing"; refusals-and-fallback "Billing and rate limits"; fetched 2026-09-29): message entries are the executor's and sum to the top-level counters; advisor_message entries are listed in advisor_iterations (message_ordinal, own model, counters, 5m/1h split, tier fields null) and counted in the window of the first row carrying them; advisor_totals and totals_including_advisor sit beside the unchanged executor totals. Each message adds cache_creation_5m/1h and speed, service_tier, inference_geo (null when absent). The split is the message entries' own splits added up, because on multi-iteration rows the top-level cache_creation object holds the first iteration's split (3,706 of 3,706 rows on this host); the top-level object only when a row carries no entries. usage.complete is false, with iteration_issues naming the reason, for an unread entry (fallback_message, compaction, other types, non-object or non-numeric), top-level counters unequal to the message entries, a successful advisor result without its entry, an advisor call without a result, or an advisor entry without a model. The aggregate sums them. Run-mode exit codes are unchanged. On this host's 4,667 transcripts with messages: 143 become incomplete (105 advisor results without usage, 48 calls without a result, 1 fallback, 1 inconsistent); 2,070 advisor iterations; message splits equal the combined counter on 201,150 of 201,151 known. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Quote the hooks reference and the M12 row exactly in kernel comments Comment-only: a quote check against the fetched bytes (sources.json in evidence/artifacts/pra-u2-differential-20260929) found two kernel comments whose quotation marks enclosed text with the source's markup removed. The hooks reference sentence now keeps its backticks, the E2E README M12 row is paraphrased without quotation marks, and the Read count names the text results it counts (133 more hold a non-text block). No code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add U2 host scans, upstream sources and differential allow-list evidence/artifacts/pra-u2-differential-20260929 (new): the count-only scans that verified B9's usage.iterations structure, the hook row shapes of item 1 and the large-result shapes of item 4 on this host's transcripts (producers and their outputs verbatim: key names, enum values and counts only, no ids, paths or text), the two checks that run the committed kernel's transcriptUsage over the same store, sources.json (every fetched upstream document: URL, fetch time, sha256, the exact sentence each rule rests on, verified against the fetched bytes), and expected-changes.json, the allow-list the later differential stage consumes (review-u2 medium finding): the paths this stage adds, and the two pre-existing paths it changes on purpose, by_hook['(other)'] (the MCP hook-name fold) and usage.complete (B9: 143 of 4,667 transcripts with messages here). Registration in manifests/evidence.json follows in the unit's last commit (docs/lanes.md hot-file protocol). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the documented source of the Read line numbers The Read cat -n strip of item 4 had no named source (final advisor review). code.claude.com/docs/en/tools-reference ("Read tool behavior", fetched 2026-09-29, sha256 cc0eda98) states that Read "returns the contents with line numbers"; the exact form (spaces, digits and a tab before every line) stays marked as observed client behavior, 11,467 of 11,467 text Read results over 5,120 B on this host. sources.json gains the entry; the kernel comment cites it and notes that a PARTIAL view notice, which carries no number, reads as unknown. Comment and evidence only; no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the advisor usage output as ID-free in a sweep control Must-stay control, added after the implementation and not failing-first: a lanes sweep over a child transcript whose message carries an advisor call reads the advisor iteration (actor advisor_iterations 1, aggregate advisor_iteration_count 1) and prints neither the message id nor the advisor server-tool call id nor the directory (the unit rule: never emit call ids in a published field). Mutation check: a scratch kernel that adds the message's advisor call ids to each advisor record prints the call id once in the same sweep; this kernel prints it 0 times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for callState templates, causes and accessor U2 item 7, the explicit callState extension the review's dependency finding asks for (review-u2 medium: U1's rule lacks templates the M14 fixtures need, and exports no per-call not_executed flag). What U1 already covers, from a count-only scan of this host's transcripts (scans/call-states.py, 5,153 files): every observed row that did not run carries a toolDenialKind, a <tool_use_error> content or the user-rejection content. The user template is fully read. The config text "Permission to use ... has been denied." (8 rows) and the cancelled text "Not run: the response that made this tool call ..." (1 row) are read only through their denial kind; the interrupt marker "[Request interrupted by user for tool use]" is a user text block after a user-rejection result (37 of 37), never a result. The fixtures without a denial kind fail at ebcca292: cli_lanes qmd not_executed 0 != 1 for the config text (content, and toolUseResult only), the Not run text, the marker as a result (the design's constant), the rejection only in the toolUseResult and "Permission for" only in the content; and cu.callState is not a function. Cases labelled (control) read not-executed at the base already. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read not-executed templates in both fields and name each cause U2 item 7, the callState extension (review-u2 medium, U1 dependency). callState and a notExecuted accessor are exported; every template is read at the start of the result content and of the toolUseResult string (after "Error: "); the config text "Permission to use <tool> ... has been denied", the client's "Not run: the response that made this tool call" and the design's constant "[Request interrupted by user for tool use]" join the list. A call that did not run gets its cause in the vocabulary of the OTel tool_decision event (code.claude.com/docs/en/monitoring-usage, "Tool decision event" and "Tool result event"): a template's cause unless it is 'other', then the toolDenialKind, then the template's 'other', then a native declined state. Hook denials carry permission-rule (809 of 809 here), so the text decides first; the built-in safety check's "Permission for this command was denied" carries permission-rule too (11 of 11), an automatic decision the doc's "config" source names. scans/not-executed-delta.mjs compares U1's rule, copied verbatim from ebcca292, with the new one over this host's 228,531 calls: 1,228 did not run under both and 0 differ. cli_lanes.failed keeps U1's meaning (is_error; not_executed a subset): tests/test_skill_usage.py:970-974, outside this unit's paths, pins it for the Codex declined fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for M14 call states, the ledger and M15 U2 design sections 4-5 (PR-A item 7 and protocol M15) with the review's corrections, as failing-first fixtures. M14 (E2E README M14 row; preregistration thresholds.M14 states): 20 calls project callState onto the sealed names. A call with a transcript result is executed, rejected (by source, OTel tool_decision vocabulary, code.claude.com/docs/en/monitoring-usage), invalid or cancelled_with_result, never "no result"; cancelled_or_unfinished is only the calls with no result; decided is null; mcp_states and calls_without_result keep their published meaning (review mediums). Ledger (AA:319): callLedger and --call-ledger in both modes, create-only with mode 0600, refused inside any git work tree, on an existing file or a missing directory; stdout stays free of call, session and agent ids, and run-mode stdout of call and session ids (review lows). M15 (E2E README M15 row; preregistration thresholds.M15): classes in context-mode v1.0.169's exact shapes, echo-free templates tested before the echo (review high), the version notice stripped, the client's MCP texts, the Codex approval denial (rust-v0.157.1 mcp_tool_call.rs:1612), and rates under binding decision B2. At 486da6ac all 8 fail: call_states None, m15 None, aggregates undefined, cu.callLedger is not a function, and --call-ledger an unknown option (exit 2), including the refusal test's accepted control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Project call states onto the sealed M14 names per actor and server PR-A item 7 (U2 design 4.2-4.4, 4.7 with the review's corrections): measureTranscript adds call_states, aggregateMeasurements sums it, and m14State(call, result) is exported. One callState per call feeds both call_states and cli_lanes, so there is one classifier. Names follow the E2E README M14 row and preregistration.json thresholds.M14.criteria.states. A call with a transcript result is executed (succeeded, failed, interrupted), rejected (by source: config, hook, user, other, declined, the OTel tool_decision vocabulary of code.claude.com/docs/en/monitoring-usage), invalid or cancelled_with_result, never "no result" (review medium); cancelled_or_unfinished is only the calls with no result; decided is null. mcp_states keeps the #432 rule and calls_without_result its published meaning (review medium): test_skill_usage.py:828-829 and :936-939 pin both, and deriving mcp_states from callState would move a declined call without a result from unfinished to failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Classify MCP errors by M15 class per server with B2 rate bounds Protocol M15 (E2E README M15 row; preregistration.json thresholds.M15): measureTranscript adds m15.by_server, aggregateMeasurements sums it and computes the rates again, and mcpErrorClass is exported. Order (review-u2 high finding): the anchored templates context-mode v1.0.169 returns before any code echo (boundary server.ts:1196-1201, returned at :2118-2119; the deny firewall; Runtime/Batch/Index/Search errors; storage and usage errors; batch timeout; fetch failures; search throttling) are tested on the raw text; the echo is required and stripped only for ctx_execute(_file) outputs after execution (timeouts, exit-classify.ts exits, module errors, indexed exits under both labels: review lows). The outdated-version notice (server.ts:892-896) is stripped first. The client's MCP texts are read on every server: not connected, the documented idle timeout (code.claude.com/docs/en/mcp), and the MCP SDK 1.30.1 "Connection closed" and -32602 validation error; the Codex approval denial is rust-v0.157.1 mcp_tool_call.rs:1612. Rates follow binding decisions B1-B2: rate counts the six classes, every new class (a new class is our misuse until reproduced) and every unknown class or outcome; rate_lower_bound counts unknowns as successes; threshold_sensitive flags bounds on both sides of 0.01; every_error_classified replaces the design's status (B1: no new status); rate_upper_bound keeps the design's ceiling, descriptive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Write the private per-call ledger behind --call-ledger PR-A item 7's ledger (AA:319, the per-child list of tool_use_ids with each call's state; U2 design 4.5 with the review's corrections). callLedger(transcript, {window}) returns one record per attempted call: session_id, owner, tool_use_id, tool, server, M14 state and cause, native_status, background, sandbox, code_mode and the M15 class, keyed by (session_id, tool_use_id) as the E2E README M14 row requires. owner is the row's agentId, 'main' with a sessionId alone, and null when the row has neither (a Codex bridge row: U3's correction 1 supplies conversation.id and the owner kind through the bridge). --call-ledger PATH works in both modes: the sweep adds the published actor ordinal, run mode the journal label and whether the attempt was superseded. The path is checked before any work: an existing directory outside every git work tree (skill_usage.py write_out's precedent, widened), and a new file, created with 'wx' and mode 0600 (review low: an existing file's mode is never taken for 0600). Refusals and failed writes exit 2 with no report. Stdout is unchanged by the flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add item 7 and M15 host scans, sources and differential entries Evidence for U2 stage 2 (count-only scans of this host's Claude Code transcripts; no id, path, command or result text in any output). - scans/call-states.py now also counts the client's MCP texts (16 not connected, 4 idle timeouts, 1 Connection closed, 1 -32602 error), the built-in "Permission for" and "Not run" templates, and where the interrupt marker sits (37 of 37 user text blocks after a user-rejection result). - scans/kernel-m14-m15.mjs runs the kernel over every transcript: 0 call_states invariant violations over 4,349 actors, and calls_without_result equal to cancelled_or_unfinished for each. The context-mode server's 34,427 calls classify completely (0 unmatched, 0 echo_mismatch), rate 0.0028 and ceiling 0.0044. Servers outside the stack's vocabulary fold into (other), as U1's closed vocabulary does. - sources.json pins the documents and code the M14 and M15 rules rest on, each quote checked verbatim against the fetched bytes: the monitoring, MCP and env-vars docs; context-mode v1.0.169 server.ts lines, exit-classify.ts and session/db.ts (byte-identical to the installed copy); codex rust-v0.157.1 mcp_tool_call.rs; and the MCP SDK 1.30.1 files, equal to the registry tarball (its sha512 is the npm integrity). - expected-changes.json lists stage 2's added paths and the one pre-existing path that can change (cli_lanes not_executed; 0 calls here), with what stays unchanged by design and why. The kernel and test comments now cite the retained scan's counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a failing-first test for the M15 threshold at rounding precision Every kernel share is rounded to four places, so a server just over the M15 threshold (preregistration.json thresholds.M15, at most 0.01) can print as 0.01: 202 of 20,100 calls is 1.005%. The B2 flag threshold_sensitive must therefore compare counts, not rounded rates. At 7810ed70 the first case fails: 190 infrastructure errors and 12 unknowns of 20,100 calls give (rate, rate_lower_bound, threshold_sensitive) = (0.01, 0.0095, False) where True is right. The two controls (exactly 1%, and both bounds over) pass at the base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare the M15 threshold with counts, not the rounded rate threshold_sensitive (binding decision B2) now tests errors * 100 > attempted for each bound: preregistration.json thresholds.M15 allows at most one infrastructure-class error in 100 calls, and a share is rounded to four places, so 202 of 20,100 calls (1.005%) printed as 0.01 and read as within the threshold. rate, rate_lower_bound and rate_upper_bound stay rounded like every kernel share; a grader compares the published counts (infrastructure_errors, new_class_errors, unknowns, attempted), never the rounded rate. scans/kernel-m14-m15.json (run with the kernel before this change) keeps its values: recomputed from its counts, every server's flag is the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for item 6, RTK M1 fields and B8 parts What: fixtures for PR-A item 6 (empty and wait-notice returns, background tasks without a terminal notification at the final message, final_return per actor and in aggregates, run-level exit 1), the rtk-claude M1 numerator (rtk_parts.eligible_call_states and observed_covered_succeeded_calls), and binding decision B8 (heredoc and arithmetic calls parsed, unknown calls counted once and left out of the eligible-part denominators, unknown_call_share and the 5% status rule). Why: U2 design sections 6 and 7 and binding decisions B5 and B8. The background-task sources (Bash backgroundTaskId, Monitor and Workflow taskId, async Agent agentId), the terminal <status> set and TaskStop come from count-only scans of this host's transcripts; code.claude.com/docs/en/ tools-reference ("Background commands") says a foreground subagent's background command stops at its final response. rtk-ai/rtk v0.50.0 (tag 1d87b8e7) src/discover/registry.rs rewrite_command_precompiled returns None for any heredoc or $(( command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Mark empty, wait-notice and pending-background returns incomplete What: PR-A item 6. returnQuality reads a child's journal result (a string, the E2E { answer, evidence } schema by its answer, or any other value by its leaves); an empty or wait-notice return is an issue, so the child and its run are incomplete and the CLI exits 1. finalReturn gives every measurement a final_return (the final assistant row's kind, whether its text is empty or a wait notice, and the background tasks still running at that row); a child that left one running is incomplete too. Aggregates count actors by final_return status and final kind. Why: #381 AA-PLAN item 6 and U2 design section 6, with three evidenced departures. waitNotice is the design's rule as a linear word scanner (no backtracking pattern; CodeQL js/redos has flagged this kernel), plus the typographic apostrophe. A value with no string at all is not a wait notice (the design's leaf rule made { ok: true } one). Background tasks come from Bash backgroundTaskId, Monitor and Workflow taskId and async Agent agentId, and end only at a <status> of completed, failed, killed or stopped or at a TaskStop that names them: count-only scans of this host found Monitor events without a status and no notification after a TaskStop of a Bash, Monitor or Workflow task (88 of 88). The client stops a foreground subagent's background command at its final response (code.claude.com/docs/en/tools-reference, "Background commands"). final_return is not_applicable without provider message ids (the Codex bridge's rows) and unobserved past the window end (the review's finding on the sweep). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Parse heredoc and arithmetic calls into RTK parts; add M1 success What: binding decision B8 and the rtk-claude M1 fields. shellParts reads the command unit by unit with U1's frame machine (step(): quotes, escapes, comments, $(( )) and (( )), "$( )" and backquote frames, and the redirection forms of & and |), treats here-document bodies as data (any delimiter word, heredocWord), and returns null only for text it cannot read. rtk_parts counts an unclassifiable call once in unknown_calls and adds none of its parts to the eligible, ineligible, observed or replayed counters; explicit-rtk, log/find and proxy counters read the command's own parts, so M-R3/M6c's zero counter keeps a violation in a call M-R1 cannot classify. New fields: unknown_call_share, eligible_call_states (M14 states of calls with an eligible part) and observed_covered_succeeded_calls (the per-child-task M1 numerator, B5). status is incomplete only when unknown calls exceed 5% of calls, on the counts, and an aggregate applies that rule to its sums. Why: B5, B8, U2 design 7.3. bash(1) Here Documents and DEFINITIONS; POSIX.1-2024 XCU 2.7.4. rtk-ai/rtk v0.50.0 (tag 1d87b8e7) src/discover/registry.rs rewrite_command_precompiled returns None for any heredoc or $(( command, so the eligible parts of such calls, unknown before, now count as observed uncovered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Begin a heredoc body at a newline of its operator's frame depth What: shellParts reads the bodies a newline begins only for the here-documents whose operators sit at that newline's frame depth or deeper, so a newline inside "$( )" no longer begins an outer body, and a here-document a substitution leaves open is read after the enclosing line. At most 64 here-documents wait at once (a command with more is unknown), which keeps the scan linear. Two fixtures pin the two cases, and two timing shapes join the linear-time checks. Why: run on 2026-09-29 under GNU bash 5.2.21 and dash, the outer body of "cat <<'EOF' && x=$(echo a<newline>echo b)" is the line after the substitution (the previous rule returned null, an unknown call), and "x=$(cat <<EOF)" takes its body from the lines after its line (bash warns "command substitution: 1 unterminated here-document"). bash(1) Here Documents; POSIX.1-2024 XCU 2.7.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document the U2 measurement fields and extend the sweep limits What: the workflows README gains a "U2 measurement fields" section (hook_context rows, blocks and claims; loaded references and actors; large-result shapes and the section-1 figures; the usage tier split and advisor iterations; the M14 call_states table; the private --call-ledger contract; the M15 class table and its bounds; item 6's return_quality and final_return), the B8 part rule and the rtk-claude M1 fields in the --rtk-check paragraph, the new exit-1 conditions, the ledger in the sweep example and the unobserved final return of an actor past the window end. The skill-usage README says which U2 fields Codex actors carry and which parts belong to the Codex adapter. The sweep's limits string gains the same U2 limits. Why: U2 design section 11 and review-u2 (the run-mode stdout contract, final_return not applicable on Codex actors, the Codex ledger owner); every rule cites its source beside it in child-usage.mjs and in evidence/artifacts/pra-u2-differential-20260929/sources.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate the workflows SHA256SUMS after the final kernel edit What: the checksums of child-usage.mjs and test-child-usage.mjs, the two files U2 changed in this directory; the other twelve are unchanged. Why: U2 design section 10, step 6 (sha256sum -- *.mjs *.js *.json > SHA256SUMS, then sha256sum --check --strict SHA256SUMS, which exits 0); the byte-identity check in this README reads the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the U2 differential, B8 property check and item 6 scans What: differential.mjs (both kernels as their own CLIs, every differing leaf path classified against expected-changes.json, which gains stage 3 and machine-readable paths), combine-chunks.mjs, the sweep differentials over W_AA without and with --rtk-check and the run-mode differential over 232 runs (0 unexpected paths in each), shell-parts-property.mjs (2,000 generated commands bash accepts, all split as built), parts-compare.mjs (the two splitters over W_AA's 30,673 Bash calls with bash -n on every disagreement), waa-invariants.py (the design's real-window invariants) and the count-only item 6 scans (background-task sources, notification statuses, TaskStop, final rows). The README gives the figures. Why: U2 design 10 steps 3 to 5 and review-u2's differential finding; binding decisions B7 (W_AA), B8 and B5. Counts only: no ids, paths, host names or command text; the sweep ran on a frozen copy of the window's transcripts, deleted after the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cite the Monitor tool's own stop rule in the item 6 comment What: the comment above finalReturn quoted the "Background commands" rule for monitors too; it now quotes the Monitor tool section's own sentence (the monitors of a stopped subagent "stop with it") and says what both mean for a return: it did not wait for the task's outcome. The workflows SHA256SUMS is regenerated after this final kernel edit (sha256sum --check --strict exits 0). A comment only: no output changes. Why: code.claude.com/docs/en/tools-reference, "Monitor tool" (the fetched copy's sha256 is the one in sources.json); a citation must say what its source says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add stage-3 sources and fold host server names in the invariants What: sources.json gains the four documents stage 3's rules cite: the tools reference's "Background commands" and "Monitor tool" sections (the same fetched bytes as its Read entry), rtk-ai/rtk v0.50.0 src/discover/registry.rs (tag commit 1d87b8e7: has_heredoc and rewrite_command_precompiled), POSIX.1-2024 XCU 2.7.4 and the installed GNU bash 5.2.21 manual's Here Documents, each with sha256 and the sentences a rule rests on. waa-invariants.py folds server names outside the stack vocabulary into (other), as scans/kernel-m14-m15.mjs does, and waa-invariants.json is regenerated from a fresh sweep of the same window (every other figure unchanged; the copy of the window's transcripts was deleted after the run). Why: the evidence README says sources.json holds every source a U2 rule cites, and published evidence names only the stack's servers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Register the U2 files in manifests/evidence.json What: the new hashes of the six listed files U2 changed (child-usage.mjs, test-child-usage.mjs, the workflows SHA256SUMS and README, tests/test_token_measurement.py, tools/skill-usage/README.md) and the 44 files of evidence/artifacts/pra-u2-differential-20260929, added with scripts/host_receipts.py register_file. component_matrix.py --write and new_host_grand_list.py --write change nothing else. Why: the hot-file protocol (docs/lanes.md): every shared hot-file edit in the branch's last commit; python3 scripts/validate.py exits 0 (hashed_files 8220, status passed). At merge, take main's manifest and re-register these files as the protocol says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for grading M15 on the ceiling What: the review's high finding as tests. A server whose every call fails with the client's "No such tool available" <tool_use_error> (an invalid call) must read over the 0.01 threshold; each class outside the named groups (policy_deny, remote_fetch, search_throttle, rejected, invalid, cancelled_with_result, and a class no list names) alone at 2 of 100 calls is over; the invoked command's own exit stays excluded; the rounding cases gain the ceiling, its count verdict over_threshold and unassigned_errors, the residual of the ceiling after the named groups. Why: the U2 design 5.3 grades on rate_upper_bound until a dated amendment assigns the other classes, and the Gate A binding decisions apply the harder-to-pass reading; the frozen M15 row (evidence/artifacts/token-adoption-e2e-20260926/README.md, M15; preregistration.json thresholds.M15) excludes only the invoked command's non-zero exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test each unassigned M15 class in its own failing-first test What: the per-class ceiling checks (policy_deny, remote_fetch, search_throttle, rejected, invalid, cancelled_with_result, a class no list names, and the excluded invoked_command_exit) move out of the reviewer-input test into their own test, so they run, and fail, on the kernel before the fix instead of sitting behind the first assertion. Why: a test that never failed proves nothing; the U2 design 5.3 grades on rate_upper_bound until a dated amendment assigns these classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Grade M15 on the ceiling until Amendment 4 assigns the classes What: finishM15Row publishes unassigned_errors (the residual of the ceiling after the infrastructure, new and unknown groups) and over_threshold (the ceiling's count against 0.01: errors * 100 > attempted), and threshold_sensitive now compares the two published bounds, rate_lower_bound and rate_upper_bound. rate and rate_lower_bound keep their meaning as descriptive figures. Why: the review's high finding. A server whose every call fails with the client's "No such tool available" <tool_use_error> read rate 0, rate_lower_bound 0 and threshold_sensitive false. The U2 design 5.3 grades on rate_upper_bound until a dated amendment assigns the other classes, the Gate A binding decisions apply the harder-to-pass reading, and the frozen M15 row (token-adoption-e2e-20260926 README.md M15) excludes only the invoked command's non-zero exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document M15 grading on rate_upper_bound in README and limits What: the workflows README splits the old "not infrastructure" row into the six unassigned classes (graded as errors until Amendment 4 assigns them) and the excluded invoked_command_exit, and states that grading reads rate_upper_bound with over_threshold as its count verdict; the sweep's U2 limits string says the same. Why: the README and the limits string pointed graders at rate, which scores unassigned classes as successes (the review's high finding); the U2 design 5.3 and 12 name rate_upper_bound as the M15 field until the dated amendment, and the frozen M15 row excludes only the invoked command's own exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate the workflows SHA256SUMS after the M15 grading fix What: the child-usage.mjs line of the workflows SHA256SUMS takes the kernel's new sha256; every other line is unchanged, and `sha256sum --check SHA256SUMS` reads OK for all 14 files. Why: the kernel changed in the M15 grading fix and its limits string; the SHA256SUMS file is the workflows directory's integrity record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remove the apostrophe that ended the U2 limits string early What: the M15 sentence of the single-quoted U2_LIMITS string said "the invoked command's own exit"; its apostrophe closed the string and the kernel failed to load (node --check: SyntaxError: Unexpected identifier 's'; 282 Python tests failed with it at 01af2f62). The sentence now reads "a non-zero exit of the command it ran". Why: 1a189bdb introduced the quote; the kernel must parse. Found by the acceptance run of tests.test_token_measurement and the other suites (repair-round log, not published). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate the workflows SHA256SUMS after the limits quote fix What: the child-usage.mjs line takes the sha256 of the kernel that parses again; `sha256sum --check SHA256SUMS` reads OK for all 14 files. Why: the previous regeneration (01af2f62) recorded the kernel with the broken U2_LIMITS string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-run the U2 differentials and M15 scans at the repair kernel What: expected-changes.json gains a repair-round stage that allows the two new m15.by_server keys (unassigned_errors, over_threshold); waa-invariants.py folds succeeded, invoked_command_exit and unassigned_errors and computes the ceiling as finishM15Row does. The three differentials (sweep over W_AA 254,003 new paths, twelve rtk windows 286,988, run mode 770,476; 0 unexpected, exit 0 each), the W_AA invariants, the ledger check and scans/kernel-m14-m15.json (5,283 files) are re-run at kernel sha256 c1d9ad16; their effects equal the earlier run's. The README records the new counts and the real-data case of the finding (plugin_context-mode: 2 invalid calls, rate 0, ceiling 1). Why: the kernel's M15 output changed in this repair round, so the recorded differential and M15 evidence had to describe the final kernel (U2 design 10; review-u2's differential finding: an explicit allow-list, equality everywhere else). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-register the repair-round files in manifests/evidence.json What: the new sha256 and byte counts of the twelve files the repair round changed (child-usage.mjs, the workflows README and SHA256SUMS, tests/test_token_measurement.py and eight files of evidence/artifacts/pra-u2-differential-20260929), written with scripts/host_receipts.py register_file; component_matrix.py --write and new_host_grand_list.py --write change nothing else. Why: the hot-file protocol (docs/lanes.md): every shared hot-file edit in the branch's last commit. python3 scripts/validate.py exited 1 on the twelve hash mismatches before and exits 0 after (hashed_files 8220, status passed). b2dd1eb7, the earlier registration, now sits mid-branch; at merge, take main's manifest and re-register every file the branch changed, as the protocol says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the pre-fix kernel and an unexamined cause in the M15 texts What: the reviewer-input test's docstring names b2dd1eb7, the kernel before the fix, instead of "the base kernel" (fe9f511b has no m15 at all); the evidence README no longer gives a cause for the 2 lines the store scan could not parse, since the scan prints only their count. Why: published evidence and test text state only what was observed (docs/acceptance-evidence-policy.md: claims quote the recording's own output). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-register the two reworded files in manifests/evidence.json What: the new sha256 and byte counts of tests/test_token_measurement.py and evidence/artifacts/pra-u2-differential-20260929/README.md after 54774c0f, written with scripts/host_receipts.py register_file; component_matrix.py --write and new_host_grand_list.py --write change nothing else. Why: the hot-file protocol (docs/lanes.md) keeps the shared hot-file edit in the branch's last commit; python3 scripts/validate.py exits 0 (hashed_files 8220, status passed). b2dd1eb7 and 962687be, the earlier registrations, sit mid-branch; at merge, take main's manifest and re-register every file the branch changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for the U3 Codex measures PR-A U3 step 1 (research-u3.design.md commit 1, amended by its review). Tests only. At the base fe9f511b the 10a and 10b tests fail; the tests of later design commits are marked pending (expectedFailure), so a marked test that starts passing fails the run until its commit lifts the marker: - 10a marker split and codex_hook_context (CodexMarkerSplit; the retained sub-agent test now asserts the split); - 10b role grouping (CodexRoleGroups); - 10g route values (commit 4) and spawn join states (commit 5), with the integer fork_turns form, a per-field route basis, follow-ups and an ID-free check with negative controls (CodexRouteValues, CodexSpawnJoin); - the Codex normalization (commit 6), 10e nested attribution (commit 7) and legacy spans (commit 8) (CodexCommandNormalization, CodexCodeModeAttribution). Must-stay controls in the same commit: the configured route, E1 and E4 at this base, the legacy lane counters, model call ids after an exec, the wait and wait_agent carriers, the unscanned outer exec code (F2), the retained isolated fixture's direct items, one actor per spawn case. Sources: openai/codex rust-v0.157.1 (36650394) protocol.rs:772-779, :2901-2916, :3116-3191, :3293-3350, :3534-3544, :4382-4390; items.rs:364-370; models.rs:958-993; hook_runtime.rs:128-154, :848-872; spawn.rs:126-130, :216-226, :253-299; child_config.rs:196-253; multi_agents_spec.rs:217-241, :646-650; policy.rs:94-112; description.rs:19-24; globals.rs:36-48; user_shell.rs:199, :453-480; the frozen launch matrix (preregistration.json seed-binding-1 to 5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the Codex marker into inherited and injected PR-A item 10a (research-u3.design.md section 1, with the review's kinds and window findings). The legacy `marker` keeps its meaning (the marker in any developer message). Additive fields: - marker_inherited / marker_injected: the marker per content item, in a record copied from the parent (ordinal below subagent_history_start_ordinal) or in the session's own record; - marker_injected_kinds: injected items by content kind, from internal_chat_message_metadata_passthrough.content_item_kinds, (none) when the kinds do not align with the items or are not strings; - group marker_inherited_sessions, marker_injected_sessions, marker_inherited_only_sessions and marker_injected_by_kind; - measurement.codex_hook_context {inserted, with_marker, inherited, inherited_with_marker} over developer items of kind hooks.additional_context: own items in the window of their record, copied items once, in the window of the child's first own record, so adjacent windows add up; groups sum it. The kernel's hook_context (Claude attachments) is unchanged and reads zero for Codex; the README says so. Sources: openai/codex rust-v0.157.1 (36650394) core/src/context/hook_additional_context.rs:15-22, core/src/hook_runtime.rs:128-154, :848-872, context-fragments/src/fragment.rs:35-53, protocol/src/models.rs:958-993, protocol/src/models/item_metadata.rs:5-7, protocol/src/protocol.rs:3180-3185. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Group Codex sessions by custom-agent role PR-A item 10b (research-u3.design.md section 2). Additive fields: actors[].role, a top-level sessions_by_role and groups.workers_by_role (the lane aggregate of the workers of each role). A session's role comes from its first session_meta: agent_role, which also reads from agent_type, else the agent_role (or agent_type) of its source.subagent.thread_spawn. It is trimmed as the spawn handler trims it, with Rust str::trim's Unicode White_Space set (not Python's strip, which also removes U+001C to U+001F); an empty value or a non-string is no role. A role that is not name-shaped is (other), a sub-agent without a role (none), every other session (root). Sources: openai/codex rust-v0.157.1 (36650394) protocol/src/protocol.rs:2904-2913, :3153-3155; core/src/tools/handlers/multi_agents_v2/spawn.rs:126-130; core/src/agent/child_config.rs:86-98 (the configured default role); Unicode PropList.txt White_Space. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for Codex usage counters and routes PR-A U3, design commit 4 (research-u3.design.md section 3) with binding correction 4 of the U3 build (u3-corrections.md). Tests only; at this head they fail on assertions: - CodexUsageCounters: cache_write_input_tokens is differenced like the other cumulative counters (30 then 45 across two snapshots, from a child's copied baseline too); a rollout without the field reports it as null, never 0, with no usage gap, and a group total is null when an actor lacks it; max_request_input_tokens keeps each attempt's largest last_token_usage.input_tokens over its counted, non-repeated snapshots. - CodexRouteValues: a route value that is not a string is null (a bool is an int in Python and str(True) is name-shaped). Sources: openai/codex rust-v0.157.1 (36650394) codex-rs/protocol/src/protocol.rs:2233-2253 (TokenUsage, serde default on cache_write_input_tokens), :2268-2275 (TokenUsageInfo), :3328, :3344-3345 (TurnContextItem model and effort). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep Codex route values and add cache-write usage PR-A U3, design commit 4 (research-u3.design.md section 3) with binding correction 4 (u3-corrections.md). It changes delivered provider_usage.attempts[] values, so it is its own commit with a before/after host differential. - effort: any name-shaped string is kept (none, minimal, ultra, persistent and custom strings were null), any other string is (other), a non-string is null (effort_key). - configured_model: a name-shaped model with at most one provider segment in front is kept (cx/gpt-6-astra read (other)); a path, '..', more segments or an empty segment is (other); a non-string, which safe_key turned into text ('True', '7'), is null (model_key). - cache_write_input_tokens joins CODEX_COUNTERS and is differenced like the others; being serde(default), a rollout without it reports null, never 0, and its absence is no usage gap, so complete keeps its meaning. A group total is null when an actor's is (the existing rule). - attempts[].max_request_input_tokens: the largest last_token_usage.input_tokens of the attempt's counted snapshots. - A token_count whose info is not an object is a gap instead of an AttributeError. Sources: openai/codex rust-v0.157.1 (36650394) codex-rs/protocol/src/openai_models.rs:59-72 (ReasoningEffort), protocol/src/protocol.rs:2233-2253 (TokenUsage), :2268-2275 (TokenUsageInfo), :3328 and :3344-3345 (TurnContextItem). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for the Codex spawn join states PR-A U3, design commit 5 (research-u3.design.md section 4). Tests only; the new ones fail on assertions at this head, the pending ones stay marked until the fix: - c22, a legacy parent: a legacy rollout persists SubAgentActivity items of kind completed only, so its child reads the new join state parent_without_started_item (requested unknown, follow-ups counted); - c23, a V1 spawn (namespace multi_agent_v1): its history comes from fork_context, so fork_context false reads none, and a V1 resume_agent with the child's id is a follow-up; - follow-ups are null when the parent was not scanned (c13, c19); - subagent_spawns counts every state (requested.fork_turns and fork_consistent pinned); - requested_fork_turns follows the spawn handler: str::trim, ASCII case-insensitive none/all, usize::from_str with an optional '+' and ASCII digits within u64, nonzero; U+001C, '2_000', non-ASCII digits, bools and floats are invalid; - a forked child's copied spawn call, sibling started item, turn context and settings stay the parent's: with the parent out of the roots the sibling reads parent_not_scanned, never parent_mismatch. Amended pending expectation (with evidence): c11's effort basis is agents_default_or_model_default, not model_default, because an [agents] default effort applies before the model's default effort. Sources: openai/codex rust-v0.157.1 (36650394) core/src/tools/handlers/multi_agents_v2/spawn.rs:216-226, :253-299; core/src/agent/child_config.rs:62-99, :196-253; core/src/tools/handlers/multi_agents_spec.rs:217-266, :591-665; rollout/src/policy.rs:94-120. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Publish Codex sub-agent spawn states from an in-memory join PR-A U3, design commit 5 (research-u3.design.md section 4, with the review's fork_turns, resume, route-basis and privacy findings). Additive: actors[].spawn for each sub-agent and subagent_spawns, the count of every state. scan_lanes_file keeps private join facts from the session's own records before until, whatever since (never the records a forked child copied from its parent): its thread, parent thread, task path and raw role, own spawn_agent calls (fork_turns, fork_context, agent_type, model, reasoning_effort and the parent's turn route at the call), own started SubAgentActivity items, turn routes, turns, the first own settings snapshot and follow-up targets. scan_codex_lanes indexes them over every scanned rollout and publishes states only: join (joined, activity_without_spawn_call, parent_mismatch, the new parent_without_started_item for a scanned parent without a started item, as a legacy rollout persists none, and parent_not_scanned), requested, effective, fork_consistent, role_state, route_vs_request, route_vs_parent_turn, route_changes_within_child, a per-field expected_route_basis, reroute_evidence and followups (followup_task target or V1 resume_agent id; null for an unscanned parent). requested_fork_turns mirrors SpawnAgentArgs::fork_mode; a positive JSON integer reads as last_n (the frozen launch matrix writes 2), and a V1 spawn reads fork_context. Non-string ids, targets and route values are ignored instead of raising. Sources: openai/codex rust-v0.157.1 (36650394) core/src/tools/handlers/multi_agents_v2/spawn.rs:126-130, :216-226, :253-299; core/src/agent/child_config.rs:62-121, :196-253, :283-299; core/src/tools/handlers/multi_agents_spec.rs:217-266, :591-665; protocol/src/protocol.rs:1403-1410, :2192-2231, :2901-2916, :3116-3191, :3293-3350, :4382-4390; protocol/src/items.rs:364-370; rollout/src/policy.rs:94-120, :141-204. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for the Codex command normalization PR-A U3, design commit 6 (research-u3.design.md section 6, with the review's shell_script, unresolved-command and window findings). Tests only; the new ones fail on assertions at this head, the pending ones stay marked until the fix: - a shell is typed by its name or file stem, case-sensitively, as Codex types it: pwsh, powershell and cmd (Windows paths included) are not POSIX, zsh, bash and sh are, and a name Codex does not know (dash) runs its OS fallback shell, which the rollout does not record, so it is unresolved (codex_commands.unknown_shell); M4 reads incomplete, and without --rtk-check no unknown call is added; - codex_commands count a call once, at its first record, inside [since, until), a function_call and its own CommandExecution item being one call, so adjacent windows add up; - a user_shell or interaction item leaves no tool_use and no result (nothing in M3, no orphan), and a group's M4 status stays incomplete for an unresolved command although the kernel recomputes it; - with RTK replay (rtk 0.50.0 on Linux) an unresolved command is an explicit unknown call; at this head a pwsh argv was replayed as measured text (calls 2, unknown 0, measured). A local RTK_REPLAY_SUPPORTED gate mirrors tests/test_token_measurement. Sources: openai/codex rust-v0.157.1 (36650394) shell-command/src/shell_detect.rs:39-59, :315-334; core/src/shell.rs: 22-49; core/src/tools/handlers/unified_exec.rs:27-50, :99-144; protocol/src/protocol.rs:3534-3544; core/src/tasks/user_shell.rs: 142-146, :190-205, :453-481. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for Codex shell typing and -c options PR-A U3, design commit 6 (research-u3.design.md section 6), second test step. A probe at 0a9bada8 showed three readings the first test step did not pin; tests only, and at this head they fail on assertions: - an argv program is typed by its file stem with / and \ as separators (a Windows Git Bash path is POSIX, CMD.EXE is cmd), while exec_command's shell is typed as Codex does: its file stem again and again, case-sensitively (pwsh.exe.bak is pwsh, PWSH is unknown), a non-string shell fails ExecCommandArgs (unknown), and null is the session shell; observed codex_commands None; - with -c among the short options the first non-option argument is the script (-cl, -e -c, -o pipefail -c); observed M4 shell_fetch 1 of 3, as the joined -cl and -o argvs hid their curl; - rtk replay of a -ec script: observed eligible 0, ineligible 1 (the joined argv), expected eligible 1. The legacy shell_script reading of a -cl argv is pinned as must-stay. Sources: bash(1) INVOCATION and OPTIONS (-c, -o option); openai/codex rust-v0.157.1 (36650394) shell-command/src/shell_detect.rs:39-59, :315-334; core/src/tools/handlers/unified_exec.rs:27-33, :117-121. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve Codex shell calls by the shell that ran them PR-A U3, design commit 6 (research-u3.design.md section 6, normalization A for U1's commandInvocations), with the review's shell_script finding: shell_script keeps its reading for the legacy lane counters, and the PR-A measurement reads each shell call through a separate resolver. It can change delivered PR-A fields, so it is its own commit with a host differential. - An argv runs its program, typed by its file stem with / and \ as separators: sh, bash, zsh, dash and ksh with -c among their short options run the first non-option argument (bash(1) INVOCATION; each o or O takes the next word), which widens the design's "cluster ending in c" to -cl and -o pipefail -c; pwsh, powershell and cmd (ASCII case-insensitive, as Windows resolves names) are unresolved. - exec_command's shell is typed as Codex types it: the value, else its file stem again and again, case-sensitively; any other value, or one that is not a string, runs an unrecorded fallback shell (unknown). Absent or null is the session shell, read as POSIX. - An unresolved call is a Bash call with no text, counted in measurement.codex_commands; m4.status reads incomplete (actor and groups, as the kernel aggregate recomputes it), and with --rtk-check it is an explicit rtk unknown call. The kernel is unchanged. - user_shell and unified_exec_interaction CommandExecution items are no model tool calls: no tool_use, no result, counted apart. - codex_commands counts a call once, at its first record in the window. Removes the three commit-6 pending markers. Sources: openai/codex rust-v0.157.1 (36650394) shell-command/src/shell_detect.rs:39-59, :315-334; core/src/shell.rs: 22-49; core/src/tools/handlers/unified_exec.rs:27-50, :99-144; protocol/src/items.rs:243-285; protocol/src/protocol.rs:3534-3544; core/src/tasks/user_shell.rs:142-146, :190-205, :453-481; bash(1) INVOCATION and OPTIONS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the frozen fork_turns launch forms through the spawn join PR-A U3, the stage's binding correction for design commit 5: M11's frozen launch forms are fork_turns none, integer 2, all (agent_type stack-researcher), all (agent_type null) and resume. The classifier test already read each value, but the join fixtures held no explicit "all" and no null agent_type. This control joins all five end to end: joined, requested fork_turns and fork_n, role, history, turns, fork_consistent, role_state and followups, with the ID-free report check. "resume" is no spawn argument (c08 reads it invalid, and upstream fails such a call), so the resumed finished researcher child is its own spawn plus a followup_task to its thread and a second own turn. A control, not failing-first: it passes at 1bbf81e9, as commit 5 reads every form. A scratch negative control (the integer form read invalid, "all" read as the default) made it fail on its assertion, at m02. Sources: evidence/artifacts/token-adoption-e2e-20260926/ preregistration.json tasks 64-68 (launch), RUNBOOK.md:411-417; openai/codex rust-v0.157.1 (36650394) core/src/tools/handlers/ multi_agents_v2/spawn.rs:253-299; multi_agents_spec.rs:217-241. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for Codex nested attribution bounds PR-A U3 design commit 7 (research-u3.design.md section 7, with the U3 review's position, item-kind and web_search_call findings). Tests only; at cd04dc34 all five fail, beside the three pending commit-7 tests: - a web_search_call id is a model call id, so an item carrying it stays direct inside an open exec span (sandbox_operations 1 at cd04dc34); - only emitted items (CommandExecution, McpToolCall, web.search Extension) count as nested or unattributed (code_mode absent); - the turn aliases turn_complete, turn_started and turn_failed end nesting, and another direct call no longer does (1 != 2); - the code_mode counters count once in the window of their first record, so adjacent windows add up (code_mode absent); - groups sum the code_mode counters (code_mode absent). Sources: openai/codex rust-v0.157.1 (36650394) protocol/src/models.rs: 1182-1203 (WebSearchCall), rollout/src/policy.rs:56, :94-112, protocol/src/items.rs:57-61 (TurnItem::WebSearch), protocol.rs: 1403-1415 (task_started/turn_started, task_complete/turn_complete), code-mode-protocol/src/description.rs:19-24 and lib.rs:51-52. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Attribute Codex code-mode items by exec position in the turn PR-A U3 design commit 7 (10e normalization B), with the U3 review's position, item-kind and web_search_call findings. An emitted item (CommandExecution, McpToolCall, web.search Extension) whose id is no model call id is now nested when an own exec call came earlier in its turn; before, nesting ended at the exec return or at the next direct call. A cell keeps running after exec returns, through yield_control() and the code-mode wait tool, so only the turn bounds it. Model call ids now include a hosted web_search_call's id. Every turn event the module reads ends a turn, the upstream aliases included. A wait call without a namespace after an exec of its turn is code mode, so its output counts under M3's code_mode carrier. New measurement.code_mode {exec_calls, wait_calls, nested_items, unattributed_items}, counted once at the first record in the window and summed by groups. The three pending commit-7 tests lose their marker. Changed meanings (the design's list): sandbox_operations, M3 by_carrier, the M4 shell_fetch/ctx_sandbox_fetch split, U1's proxy.nested and the cli_lanes carrier nested. A count-only census of this host's store (1,620 rollouts) found 271 items after the exec span in the same turn, none without an exec in its turn, and 352 wait calls, all after an exec of their turn. Sources: openai/codex rust-v0.157.1 (36650394) code-mode-protocol/src/ description.rs:19-51, lib.rs:51-52; protocol/src/models.rs:1060-1165, :1182-1203; rollout/src/policy.rs:56, :94-112; protocol.rs:1403-1415; protocol/src/items.rs:57-61; multi_agents_spec.rs:268-291. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for Codex legacy spans and outer JS scope PR-A U3 design commit 8 (10e legacy-mode spans), with the U3 review's per-exec finding and the explicit outer-JS decision the stage brief requires. Tests only; at 9799d5fa every new test fails, beside the two pending commit-8 tests (code_mode legacy and outer keys absent, M4 status not forced): - rows with no session_meta, or a history_mode other than paginated, read as legacy (no session_meta: None, None, not_applicable); - static sites: the global tools with a fetch-capable name after a dot (blanks and ?. allowed) and any bracket access, not ALL_TOOLS, x.tools, longer names or other nested tools (expected 5 sites); - legacy spans count once in the window of the exec call; - outer HTTP_SCRIPT-shaped mentions are counted, never as fetches, and a client whose isolate was not read (0.158.0, or none named) makes M4 incomplete for the actor and the group. The shared test metas now name cli_version 0.157.1, a client whose isolate was read. Sources: openai/codex rust-v0.157.1 (36650394) protocol.rs:772-779, rollout/src/policy.rs:94-112, code-mode-protocol/src/description.rs:24, code-mode-runtime/src/ runtime/globals.rs:36-48; rust-v0.155.1 (be2951ea) description.rs:20 and a byte-identical globals.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Mark Codex legacy exec spans and scope outer JS to read isolates PR-A U3 design commit 8 (10e legacy-mode spans), with the U3 review's per-exec finding and an explicit outer-JS decision. Legacy spans: a rollout that is not paginated (no history_mode, or no session_meta, is legacy upstream) persists no nested item. There an exec call whose code has a static fetch-capable site and no attributed nested item (after it, before the next exec or the turn's end) counts in code_mode.legacy_unobservable_exec_calls and _sites, once in the window of its call record, and M4 is incomplete for the actor and its groups. Sites come from a linear scan (code_mode_fetch_sites): the global tools with exec_command, web__run or mcp__*__ctx_* after a dot, or any bracket access, which can name any tool. Outer JS (the review's medium finding, decided here, not declared unnecessary): the isolate has no network access at 0.155.1 and 0.157.1, read at both tags, so outer HTTP_SCRIPT mentions (a linear scan) are only counted in code_mode.outer_http_mentions. For any other client, or none named, an exec with a mention counts in outer_http_unverified_exec_calls and makes M4 incomplete. The dated record, alternatives, host residual and overturn conditions are in tools/skill-usage/README.md; docs/decisions/ is outside this unit, so the PR-A owner accepts or rejects it. The two pending markers lift and the unused pending helper goes. Host census (count only): all 1,620 rollouts are paginated and name 0.155.1 (277) or 0.157.1 (1,343); 1,532 mentions in 1,091 of 16,410 exec calls, 51 of them in 33 calls with no nested ctx code item. Sources: openai/codex rust-v0.157.1 (36650394) protocol.rs:772-779, rollout/src/policy.rs:94-112, code-mode-protocol/src/description.rs:21, :24, :365-387, code-mode-runtime/src/runtime/globals.rs:36-48; rust-v0.155.1 (be2951ea) description.rs:20 and globals.rs (the same sha256); child-usage.mjs HTTP_SCRIPT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add failing-first tests for the Codex RTK replay agent and D7 PR-A U3 design commit 9 (10d), with the U3 review's findings on the d7.wrapped_exceptions provenance and the Claude byte-identity check. Tests only; at e79a5c18 all fail: - the Codex bridge asks the kernel for rtkAgent codex and labels rtk_parts.agent (payload option absent); - with a home settings file denying Bash(git status) and the cwd outside any project, the Codex lane replays as claude today and counts an unknown call (0, 1, incomplete), not the rewrite; - measureTranscript refuses an rtkAgent other than claude or codex (accepted today), keeps the Claude output without d7, and every fixed-config field agrees between the agents where no rule applies; - rtk_parts.d7 drops reviewed requires_raw log/find parts, prefixed or not, counts prefixed ones as wrapped exceptions, keeps unreviewed ones and marks d7 incomplete (d7 absent); - an unresolved command leaves d7 incomplete; groups and the kernel aggregate sum d7 for Codex measurements only. The fake home registers the rtk hook: a Claude directory without one makes rtk 0.50.0 print a once-a-day "No hook installed" warning on the claude path, which fails the kernel's five-exclusion probe (observed here; src/hooks/hook_check.rs:26-35, :88-135). The measure helper gains a cwd. Sources: rtk-ai/rtk v0.50.0 (1d87b8e7) src/main.rs:992-1000, src/hooks/decision.rs:61-97, :196-204, permissions.rs:56-67, :141-175, hook_check.rs:26-135; adoption/templates/codex.AGENTS.template.md (the D7 exception list); preregistration.json thresholds.M6c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replay Codex RTK parts as the Codex hook and add the D7 view PR-A U3 design commit 9 (10d), with the U3 review's findings on the d7.wrapped_exceptions provenance and on the Claude byte-identity check. Kernel: measureTranscript takes rtkAgent, claude by default or codex, and refuses any other value. rtkChecker keeps one checker per agent, each with its own five-exclusion probe, and asks rtk hook check --agent <agent>; the claude checker is unchanged. For codex, rtk_parts gains d7: the fixed-config eligible parts less the git log and find parts a digest-bound review marks requires_raw, prefixed or not. An unreviewed log or find part stays and leaves d7 incomplete, as does an unknown call. wrapped_exceptions is explicit_rtk_on_excluded_or_sensitive (M6c's zero counter, broader than the template list: stated, not narrowed) plus wrapped_requires_raw_parts. aggregateMeasurements sums d7 only over the measurements that carry it. SHA256SUMS regenerated. Bridge: skill_usage.py passes rtkAgent codex and labels rtk_parts.agent; an unresolved command, replayed as '' ("No rewrite for:" under either agent), stays an explicit unknown call and now also leaves d7 incomplete. The skill-usage README drops the "hypothetical Claude-hook routing" wording and documents d7. Why: rtk 0.50.0 maps codex to Host::Codex, which has no RTK-side permission rules; claude merges the cwd project's and HOME's Claude Bash rules, so before this a Claude deny rule on the machine running the tool turned a Codex part into an unknown call (the failing-first tests). Sources: rtk-ai/rtk v0.50.0 (1d87b8e7) src/main.rs:992-1000, src/hooks/decision.rs:61-97, :196-204, permissions.rs:56-67, :141-175; adoption/templates/codex.AGENTS.template.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document Codex RTK replay, D7 and code-mo…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR-A of the #381 token-adoption E2E. The branch extends the existing WP4
transcript tools with the preregistered measurement kernel: M3/M5 result
sizes, M4 fetch routing, RTK part eligibility for M-R1/M-R3/M6c, hook-context
claims versus insertion, MCP attempt states and per-message provider usage.
The Claude lane (
child-usage.mjs) and the Codex lane (skill_usage.py,through a Node bridge) share one kernel. This is measurement tooling: full
PR-A acceptance (M1, M15 and the other items under Residuals) and any E2E
savings claim remain open.
The last round (fixup3, 2026-09-27) repairs the four findings of the Opus
verification of fixup2 (verdict
block): M4 heredoc resolution (D1, high),the workflow checksum file (D2), offset-exact possible-fetch accounting (D3)
and this body (D4). It also adopts the verifier's recommendation 6: raw
curl/wget/gh apimisses now count as possible fetches, likeHTTP_SCRIPTmisses, so a parser false negative on these patterns cannotraise the M4 lower bound.
Changes
Whole branch (9 files, replayed onto main
55fc8d17, after #433):examples/claude-native/workflows/child-usage.mjs: the kernel. M3/M5 countUTF-8 content bytes per carrier (text blocks without wrappers), minus
witnessed exceptions from a digest-bound private sidecar. M4 counts
confirmed remote fetches per carrier, including nested ctx and Codex
sandbox fetches, and reports
routed_share,fetch_mentions_unconfirmedand
routed_share_lower_bound.rtk_partsreplays nativertk hook check(RTK 0.50.0) per simple part for M-R1 coverage; thedeterministic M-R3/M6c zero counter is separate from advisory log/find
review, and proxy parts are outside eligibility. The git option prefix
that
logFindPartand two RTK exclusion patterns share is read by alinear walk over word positions, and interpreter option words match
-[\w-]+(the CodeQL repair below). Hook-context claims,inserted context, MCP states and per-message usage are reported
separately. A sweep reports main transcripts apart from the child
population, which stays comparable with the Lane use per Claude child and Codex worker, with a frozen-window baseline for the workstation #369 receipt.
tools/skill-usage/skill_usage.py: the Codex adapter onto the same kernel.Function, custom and local-shell calls pair with their outputs; code-mode
nested operations stay out of context bytes; MCP state falls back to the
native item status; Codex measurements omit the Claude
usageobject.tests/test_token_measurement.pyandtests/test_skill_usage.py: syntheticcontrols for both lanes.
examples/claude-native/workflows/test-child-usage.mjs: one assertion nowtreats stdout-only hook context as a claim, never proof of insertion.
examples/claude-native/workflows/README.mdandtools/skill-usage/README.md:counter definitions, the gate reading and the limits.
examples/claude-native/workflows/SHA256SUMS: checksums of the changedworkflow files.
manifests/evidence.json: registration of the branch's own changed files,in the last commit (
docs/lanes.mdhot-file protocol).Builder commit handoffs, dated fixup receipts and this body stay in the
coordinator's unit notes, outside the tree. An earlier head committed them
under
units/w3/measure/; this revision removes them and their registrations.This round (fixup3):
its
<<operator runs stdin as source. That command is the text betweenthe nearest control operators around the operator, with quotes removed and
redirections and their targets dropped.
bash <<'EOF' 2>&1 | tail -n 5,bash <<'EOF' > log.txt,&&/;suffixes andpython3 - <<'EOF' | tailtherefore keep their executed bodies. Shells follow POSIX sh STDIN and
bash(1):
-cor a script operand leaves stdin as data,-skeeps it, ashell's
-equals--,-o/+o/-O/+Otake a name,--rcfile/--init-filetake a file, and-nreads without executing.sshruns stdin in the remote login shell when no remote command followsthe destination, or when the remote command reads stdin as source;
-n,-f,-N,-s,-W,-O,-G,-Vand-Qnever do. A<<insidequotes, a comment or
$(( ))opens no heredoc. Two limits remain and errtoward counting a fetch (residual N1 below):
$(is not tracked insidedouble quotes, and a quoted string that a shell runs is added without its
own heredoc resolution. The legacy
fetchKind/lanes.fetch.bash_curl_wgetcounter again follows the workflowsREADME's description (heredoc bodies count under
sshor a shell heredoc).The source comment that claimed unknown option forms stayed possible
fetches for every detector is replaced.
fetch_mentions_unconfirmedalso counts rawcurl/wgetin command position and rawgh apimatches thatexecuted-text analysis did not confirm. Both READMEs document it.
executed match confirms only the raw match at its own raw offset, anchored
at the call name, the
curl/wgetword orgh. Matches created bybackslash-newline joins, unescaping or quoted strings a shell runs cannot
offset a raw miss. The exported
executedTextstring API is unchanged.SHA256SUMSis regenerated with the documentedsha256sum -- *.mjs *.js *.json(13 entries;README.mdremoved).M4 scope
routed_share = ctx_fetch_and_index / remote_fetchesuses confirmedfetches.
routed_share_lower_bound = ctx_fetch_and_index / (remote_fetches + fetch_mentions_unconfirmed)treats every possible fetch as unrouted.The PR-H: E2E preregistration for the token-save-practice stack (frozen tasks, metrics, gate, procedure) #381 M4 >= 0.9 gate must read
routed_share_lower_bound, computedfrom the integer counts; reported shares are rounded to four decimals.
Possible fetches are raw
HTTP_SCRIPTmatches anywhere in the command orcode, raw
curl/wgetin command position (a line start, or after;,&,|,(, a backtick or$(, also behind a shell keyword or wrapper) and rawgh apiat a line start or after a separator. They include data-onlymentions such as a heredoc that writes a script; those lower the bound
conservatively and leave M4 status
incomplete. In neither count: acurlinside a quoted string the parser does not treat as run
(
ssh -o Opt=value host 'curl ...',watch 'curl ...'), behindxargs,find -execor an unrecognized wrapper, or passed to a subprocess insideinterpreter code; loops, dynamic code, external scripts and aliases.
Evidence
Classes: synthetic fixtures (static transcript analysis; no fixture command
is executed), local integration (unit and contract suites, the native RTK
hook check), and local measurement (an installed-shell probe whose heredoc
bodies only
echo). No live provider execution and no unchanged upstreamtest suite. Returned outputs are retained in the coordinator's unit notes
(
fixup3/).Verifier reproduction (verify-fixup2, "Verification gaps"). Columns are
fetchKind,shell_fetch,unclassifiableandfetch_mentions_unconfirmed:71c0a991(before)null 0 0 0null 0 0 1null 0 1 0e7d3c5a3"fetch" 1 0 undefinednull 0 1 undefinednull 0 1 undefined"fetch" 1 0 0null 0 1 0null 0 1 1The verifier's predicted table held exactly for both earlier revisions; row 6
at
e7d3c5a3had no prediction.Failing first. The final
tests/test_token_measurement.pyrun against the71c0a991module returnedRan 38 tests,FAILED (failures=145), exit 1.All 145 subtest failures are in the five new tests (66, 36, 12, 9 and 22
subtests), and the 33 existing tests pass. The new tests are
test_m4_heredoc_resolves_its_whole_simple_command_on_every_carrier,test_m4_unexecuted_heredoc_curl_wget_and_gh_api_stay_possible_fetches,test_m4_heredoc_openers_respect_quotes_comments_and_arithmetic,test_m4_confirmations_trace_back_to_raw_offsetsandtest_fetch_kind_and_legacy_lane_follow_the_heredoc_resolution. They coverevery shape the verifier listed on all three shell carriers plus
fetchKindand the legacy lane counter.
Fixup3 results:
python3 -m unittest -v tests.test_token_measurement:Ran 38 tests, OK,no skips. The RTK replay controls therefore ran (Linux,
rtk 0.50.0).python3 -m unittest tests.test_token_measurement tests.test_child_usage_suite tests.test_skill_usage tests.test_adoption_docs_consistency.RelativeLinkTests tests.test_adoption_docs_consistency.ScriptPathTests:Ran 143 tests, OK,exit 0.
test-child-usage.mjs92/92,test-envelope.mjs254/254,test-contract-mutations.mjs74/74;check-syntax.mjsandnode --checkexit 0.
executedText(both modes) andfetchKindover the 198 commands the covering tests feed the kernel. 171 areidentical; the 27 that differ are exactly new D1 controls. A fuzz of 40,000
heredoc-free inputs in each of the two modes (80,000 checks) gave 0
mismatches. Trace integrity over 20,144 inputs in each mode (40,288 checks,
1,028,897 traced characters) gave 0 offset violations.
sha256sum --check --strict SHA256SUMS: 13 OK, exit 0.git diff --check:exit 0.
bash -euo pipefail,-oe pipefail,+o pipefail,-O extglob,+O extglob,--norc,--rcfile /dev/null,-s,-s arg,-,--,sh -e,dash -o nounset, and the2>&1 | tail -n 5and> log.txtsuffixes. It did not run for
-n,-s -c,-c cator a script operand,including one after
-.python3 scripts/validate.pyat419550a2: exit 1, with SHA-256 andbyte-count mismatches for exactly the six registered files that round
changed, pending re-registration.
419550a2,python3 -m unittest -qwithTMPDIR=/var/tmp/claude-measure:Ran 6685 tests in 699.400s,FAILED (failures=2, errors=1, skipped=761), exit 1 (the 6,680 tests at71c0a991plus the five new ones). The error is this host's knownenvironment case for a worktree under
/tmp:test_order_throughputtest_cli_refuses_live_base_url_from_env_file_without_network(
credential_file_permissions:worktree). One failure is alsoenvironmental:
test_gpt6_family_tiering_20260926test_sigterm_records_the_running_call_as_interrupted_without_counting_it(the temporary directory must lie outside every repository). The other,
test_catalog_freshness_proposetest_general_publication_validator_passes_after_the_run, is the samepublication validation on the then-unregistered changes. There is no other
failure or error.
f3ec6485, after the replay ontoe82e6be7(the last replay, onto55fc8d17, kept content lines identical andvalidate.pyat FAILS=0): with TMPDIR outside every repository,python3 -m unittest tests.test_token_measurement tests.test_skill_usageran 137 tests, OK with no skips; inexamples/claude-native/workflows,node test-child-usage.mjspassed 92 of 92 (95 of 95 after the CodeQL repair),test-envelope.mjs254 of 254 andtest-contract-mutations.mjs74 of 74, andcheck-syntax.mjsexited 0;sha256sum --check --strict SHA256SUMSpassed;git diff --checkis clean;scripts/validate.pyreports FAILS=0. The replay kept every content line identical, registered the branch's 8 files, and left nounits/w3entry inmanifests/evidence.json(count 0).the tag), tag
v1.0.169resolves to589d8214d56740a28b5f7bf63167743d586b0b40.hooks/core/routing.mjsat thetag and in the installed source share blob
e92ab46b2faf6597789bdc5eb3a5a90460d9da3c.CodeQL repair (2026-09-27, content commit
fce6274d, first pushed as7ee9d03c): CI at7aba17acfailed the CodeQL check. It reported four high-severity
js/redosalerts(exponential backtracking) in
child-usage.mjs:RUN_HTTP_CODE(the node, bun and deno option word--?[\w-]+);logFindPart(the git option prefix, where--git-dirreadseither as an option taking the next word or as a lone
--option).The query help recommends removing the ambiguity. The fix:
-[\w-]+matches the same words as--?[\w-]+with one reading.words, then tries the rest of the pattern at each word it can reach.
exclude_commandspatterns that sharethe prefix. Their strings, which the RTK config is written from, are
unchanged; RTK itself compiles them with Rust's
regexcrate, which isworst case O(m * n).
Local integration checks:
logFindPart,sensitivePartandRUN_HTTP_CODE: 4,167,264 generated inputs, 0mismatches. It combined exhaustive four-word strings with 300,000 random
strings per family, and the
FIVE_EXCLUSIONSarray is identical.expressions take 3, 47 and 187 ms at 16, 20 and 22 repetitions.
exported (2 of 95 fail: 28 repetitions exceed 1 s), then pass (95 of 95).
tests.test_token_measurementandtests.test_skill_usageran 137 tests, OK.test-usage-receipts.mjspassed 18 of 18,test-envelope.mjs254 of 254and
test-contract-mutations.mjs74 of 74.test-codex-envelope.mjsexited 0 with no failures, andsha256sum -c SHA256SUMSpassed.validate.pypassed.Dated history (all 2026-09-27):
5139fdf5(GPT-6 builder): 117 covering tests passed.6b0f31a4, after an independent review with eleven findings: 130covering tests, 92 Node checks and 13 workflow checksums passed.
e7d3c5a3: interpreter heredocs and quoted interpreter code kept inthe M4 denominator (builder receipt in the unit notes).
c60899f7: ignored stdin,fetch_mentions_unconfirmedandrouted_share_lower_bound; 133 covering tests passed (builder receipt inthe unit notes). The builder's own full-suite attempt, with TMPDIR
inside the repository, returned 6,656 tests with 247 failures, 151 errors
and 783 skips. Its retained errors included fixtures that require
temporary state outside every repository; the complete failure inventory
was not retained, and the run was never acceptance.
71c0a991:validate.shFAILS=0; full suiteRan 6680 tests in 717.989s, OK (skipped=761).block(D1 to D4, recommendation 6).419550a2: results above.residual. D1, recommendation 6, D2 and D3verified; the new controls fail on
71c0a991(145 failures, all in the fivenew tests). New low defect N1 (below); D4 partly accurate (the fuzz and
trace counts, the error/failure split and the heredoc rule, all corrected in
this body).
the scratch files leave the tree, and the branch is replayed with its own
registrations in the last commit. The N1 fix follows in its own PR, per the
one-review, one-repair bound.
SOTA sources
CodeQL query help,
js/redos("Modify the regular expression to remove the ambiguity").
rtk-ai/rtk
v0.50.0(1d87b8e7):Cargo.tomlL28 (regex = "1")and
src/discover/registry.rscompile_exclude_patterns(Regex::new).regex crate docs: "all regex searches in this
crate have worst case O(m * n) time complexity".
PR-H: E2E preregistration for the token-save-practice stack (frozen tasks, metrics, gate, procedure) #381 contract:
evidence/artifacts/token-adoption-e2e-20260926/README,RUNBOOK and
preregistration.jsonM4 (all remote fetches including nestedand unclassifiable ones, routed rate >= 0.9, unclassifiable rate <= 0.1,
unknown excess
incomplete). The lower bound is the requested localextension of that contract, not an upstream parser feature.
mksglu/context-mode v1.0.169 (
589d8214d56740a28b5f7bf63167743d586b0b40):heredoc stripping, routing.mjs:228-229,
curl/wget and inline-HTTP routing, routing.mjs:727-804,
the mandated detector reference, routing.mjs:788-795,
and UTF-8 accounting, extract.ts:1060-1069.
Shell and ssh semantics:
POSIX.1-2024 sh
OPTIONS, OPERANDS and STDIN;
GNU bash 5.2 bash(1) OPTIONS, ARGUMENTS, DEFINITIONS, RESERVED WORDS,
QUOTING, COMMENTS, REDIRECTION, Here Documents and ARITHMETIC EVALUATION
(the installed 5.2.21 manual page; the GNU online manual timed out on
2026-09-27); OpenSSH ssh(1), which the
installed OpenSSH 9.6p1 manual page matches.
Interpreter entrypoints:
Python 3.13 interface options
and Node v24.21.0 stdin.
rtk-ai/rtk v0.50.0 (
1d87b8e719ce0a50c223cd93ca64dd16921f9aec):hook check,
lexer
and consumer rules.
These identify the reference implementation, not the installed binary build.
openai/codex rust-v0.157.1:
calls and outputs,
code-mode emission
and MCP item status.
ccusage v20.0.24 (
ecb676cce27cb5dd0090c7804a5cecc35e8ba805):Claude per-message deduplication
and Codex baseline deltas.
Residuals and not done
compute successful opportunities among eligible tasks separately for every
required lane (
RUNBOOK.md:564).infrastructure errors from invoked-command nonzero exits or establish the
per-server error ceiling (
RUNBOOK.md:582).JSON/uniform-tabular classification; final-return quality classification;
a private native-ID ledger for rejected, cancelled and unfinished execution;
Codex inherited-versus-injected markers, custom-role grouping, spawn fork
metadata and provider-verified routes; frozen historical baselines, task
identity joins, matched A/B comparisons and actual E2E acceptance.
"$( ... )"and shell-run strings (low; from thefixup3 repair):
openers()does not track$(inside double quotes, anda quoted string that a shell runs is appended without heredoc resolution.
Reproduced at
419550a2with the verifier's probes:git commit -m "$(cat <<'EOF'with a line-startcurlin the body givesfetchKind=fetch,shell_fetch=1,remote_fetches=1;gh pr create --body "$(cat <<'EOF'with agh apiline givesunclassifiable=1;bash -c 'cat <<EOF > x.sh ...'givesshell_fetch=1.71c0a991gave 0 for all three. The effect only lowers the routed shares,so it cannot pass the M4 gate falsely; the workflows README states the
limit. Fix: track
$(inside double quotes and resolve heredocs inshell-run strings, with failing-first controls, in a follow-up PR.
leaves as data count only through the raw patterns:
sudo -u user bash,docker exec/kubectl execand other unrecognized wrappers,{ ...; } <<EOF, a backslash-newline continuation before the opener,<<\EOFand$'...'quoting, Python-W/-Xor Node--input-typebefore stdin, and zsh- or ksh-specific options. The Python shift-syntax and
Node comment-apostrophe misses remain possible fetches.
tools/skill-usage/skill_usage.py:690-726still citesthe removed
programOf. Its legacy Codexfetch_kindnow differs from theClaude lane on
-c,-n,ssh -n,bash - script.shand a<<insidequotes or comments. M4 itself is unaffected, because the Codex lane uses the
Node kernel.
execcode(
skill_usage.py:957) is not scanned for fetches.sidecar witness review and ambiguous interleaved or resumed code-mode
associations remain outside this branch. The shared sandbox bucket does
not establish exclusive context-mode use.
55fc8d17and registers only its own changed files in the last commit(
docs/lanes.mdhot-file protocol).Recommended lane label
lane:foundation🤖 Generated with Claude Code