Skip to content

Codex rtk hook: a narrow trust tool, the qualification evidence and its decision record (first of two) - #701

Merged
seathatflowsinourveins merged 4 commits into
mainfrom
claude/99-codex-rtk-hook-20261004
Oct 5, 2026
Merged

seathatflowsinourveins merged 4 commits into
mainfrom
claude/99-codex-rtk-hook-20261004

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Scope

  • First of two pull requests that qualify RTK's Codex PreToolUse hook and make rtk init -g --codex the upstream default for Codex hosts (the user's clean-install directive of 2026-10-04). This one adds what touches no file that PR 684 owns: a narrow trust tool (tools/adoption/codex_hook_trust.py, 22 tests), the qualification evidence (evidence/artifacts/token-stack-fresh-session-e2e-20261004/: five reproducer scripts, a sanitized receipt, the two rtk probes' output, the NativeStack before snapshot, a README) and the decision record. The second pull request, written on main once PR 684 lands because the install plan's RTK row is its, adds the plan row, its acceptance and the handbook and card text. Session native-agent-stack-5f reconciles the Codex awareness block (the F4 exceptions that ten role files carry) separately.
  • Base commit: 3b8f9c8a1 (main)
  • Lane: lane:foundation
  • Owned paths touched: tools/adoption/codex_hook_trust.py, tests/test_codex_hook_trust.py, evidence/artifacts/token-stack-fresh-session-e2e-20261004/**, docs/decisions/2026-10-04-codex-rtk-hook-qualified.md, one anti-pattern-log row pair in docs/harness-defaults.md, and manifests/evidence.json (last commit, registry:). Nothing is installed or trusted on any host by this change.

SOTA sources

  • RTK: rtk-ai/rtk v0.51.0 at e001f773f80b22b7dc4c7a79521b30e35aaef026, README.md L133 (rtk init -g --codex) and L460 ("Codex | rtk init -g --codex | PreToolUse hook (updatedInput) + AGENTS.md"); the installed rtk init --help, rtk init --show --codex, rtk rewrite and rtk gain -f json.
  • Codex hook trust: openai/codex rust-v0.159.3, commit 01fc69f4026735edfdf6789820549727a4867b11: codex-rs/hooks/src/engine/discovery.rs hook_hash L775 and hook_trust_status L794-L815 (a non-managed hook runs only while its hash equals trusted_hash; otherwise untrusted or modified); codex-rs/hooks/src/config_rules.rs hook_states_from_stack L15-L62; codex-rs/tui/src/hooks_rpc.rs write_hook_trusts L58-L91 (the /hooks review persists trust with config/batchWrite of hooks.state, upsert, {"<key>": {"trusted_hash": <currentHash>}}), which the tool reproduces. The app-server schema (hooks/list: data[].hooks, errors, warnings; config/batchWrite) is the installed binary's own (codex app-server generate-json-schema).
  • Codex docs, https://developers.openai.com/codex/hooks ("Review and trust hooks", "Where Codex looks for hooks", PreToolUse), read 2026-10-04. codex exec --help of 0.159.3 lists --dangerously-bypass-hook-trust (codex-rs/utils/cli/src/shared_options.rs L61-L64 and codex-rs/exec/src/cli.rs L145 at the same commit; the repository's capture evidence/artifacts/runtime-sdk-20261003/exec-help-0.159.3.txt L65): per invocation, persists nothing, skips trust for every enabled hook, so it is not the persisted, hash-bound grant the tool writes.
  • Claude Code claude -p --output-format stream-json --verbose --include-hook-events and Codex codex exec --json --ephemeral for the session evidence; the loopback OmniRoute gateway route of examples/omniroute-codex-sdk/worker.py for the model-backed arms (no credential needed or copied).

Evidence-class table

Claim Evidence class Command / receipt
rtk init -g --codex appends one PreToolUse group after the existing ones and changes no other byte, is idempotent, reversible with --uninstall, and Codex's own hooks/list loads it without error or warning local_integration: the upstream commands on a scratch copy of the host's hooks.json, the live file hashed before and after README "Codex rtk hook qualification"
With the hook trusted, 22 of the 23 commands of four arms ran with the rtk prefix (diff is excluded by the host's exclude_commands, which the arms ran with), exit codes equal to the shell's in all seven arms, nothing blocked, a second PreToolUse hook ran in every trusted arm, a model-prefixed command got no second prefix local_integration: real codex-cli 0.159.3 and rtk 0.51.0 through the loopback gateway, scratch Codex homes, one run per arm receipt.json; codex_hook_qual.py
An untrusted hook is skipped (in that arm with the other hook); with rtk off the PATH the hook fails open and silent same same
codex_hook_trust.py on the real binary: dry run, apply with backup, idempotent second run, exit 4 without a matching hook; the qualification arms ran with it local_integration: scratch homes codex_hook_qual.py; the fake-server tests are synthetic
New: when the hook's definition changes right after the trust write, the tool at c8613fe16 reported success (exit 0); the fixed tool exits 3 and names the hook local_integration: the real codex app-server 0.159.3 in a scratch home, one run each trust_readback_race.py; receipt.json, trust_readback_race
New: rtk 0.51.0's rewrite table with upstream defaults and with the five exclude_commands of fixtures/rtk-hook-exclusions.toml, and its compact forms against the shell's (bare git log caps at 10 commits, no notice, merges dropped; --stat caps with the stderr notice; --oneline and --format without a count default to 50, no notice; the trailing newline; /usr/bin/ls; find exit 1) local_integration: one run, one host, a scratch repository (66 commits, one merge) and scratch homes rtk_behaviour_probe.py; rtk-behaviour-probe.json
New: rtk init -g --codex also creates RTK.md, appends the inert @<Codex home>/RTK.md pointer to AGENTS.md (creating it when missing) and rtk's history database, honours CODEX_HOME, is idempotent, is undone whole by --uninstall, and refuses --hook-only, --no-patch and --auto-patch local_integration: one run, one host, scratch homes (four scenarios, 13 steps) rtk_init_codex_probe.py; rtk-init-codex-probe.json
The tool's refusals and failure paths (a running codex, a failed write, a hook the writer still reports untrusted, a hook that changed, vanished or left the user layer during the write, a new discovery error, a project or managed hook, backups) synthetic tests/test_codex_hook_trust.py, 22 tests
A fresh Claude Code and Codex session on NativeStack picks the token stack up with nothing per project; rtk rewrites show in the hook events local_integration: one host, one run per session nativestack-before-snapshot.summary.md; fresh_session_e2e.sh
The upstream sources above behave as cited source_review the pinned lines
Installing the hook and the trust grant on a real host not run untested boundary: the second pull request and the command center's host steps

Delta read of c8613fe16 (cross-family, job-056): a P2, two P3s and residuals, each checked on its merits

  • P2, codex_hook_trust.py:129 (fixed). The read-back searched for the command again, so a hook that vanished or changed during the write dropped out of the check and the tool printed "trusted N hook(s)" with exit 0. Reproduced on the real binary (above). Every named hook is now looked up by key and must still be a user-layer command hook at the hash it had and trusted; a discovery error that was not there before the write fails too. Seven tests were written first (ten failures on the old tool).
  • P3, bypass flag (corrected). The flag exists in 0.159.3. My absence claim came from codex exec --help filtered through grep and head -8; the README, the decision record and the anti-pattern log (new row) now say so.
  • P3, git-log cap (retained and corrected). The claim had no retained measurement. The new probe retains it, and it showed two more errors in my own text, both corrected: (1) rtk's own default rewrites git show REV:path, diff, jq and git branch; the hook left them alone only because the host's ~/.config/rtk/config.toml (the arms ran with the host's HOME) carries the repository's five exclude_commands (anti-pattern row); (2) bare git log prints no notice (--stat does).
  • Residuals recorded: the dry run makes no trust or config edit but is not read-only (starting the app-server creates SQLite files, an installation id and bundled skills in the Codex home; measured on a scratch home); native probes ran on 0.159.3 only (0.160.0 is checked from source only); the NativeStack snapshot's counter delta (+33 commands, +297 tokens) spans other sessions, so the summary's heading now says the project-scoped block is the E2E's own.

Second read of e3ed1fe36 (cross-family, job-060)

The read-back race is closed on both revisions and the probe evidence holds. One P3 was left and is fixed here: the README said rtk's compact log formats have no cap, but rtk 0.51.0 adds -50 to --oneline, --format and --pretty when no count is given (src/cmds/git/git_cmd.rs L1819-L1825 at e001f773; I read the source and re-ran the probe on a 66-commit fixture: 50 lines against git's 66, 66 and 68). The README, the record and a third anti-pattern row now say so, and the fixture is larger than the limit. This push is text and retained probe output only: the trust tool and its tests are unchanged from the read.

For the user to confirm (not decided here): the upstream install is not hook-only, so it also appends an inert @<Codex home>/RTK.md line to ~/.codex/AGENTS.md, an instruction-file edit that the user's "yes frictionless" about authorization settings does not name. NativeStack's AGENTS.md already carries the same line. The command center has put the question to the user; the second pull request's Codex row waits for the answer, and rtk init -g --codex --uninstall removes the hook, RTK.md and the line together.

Local commands run

$ python3 -m unittest tests.test_codex_hook_trust tests.test_host_receipts tests.test_foundation_catalog tests.test_ecosystem_manifest tests.test_adoption_docs_consistency tests.test_codex_worker_lane tests.test_token_e2e_preregistration
Ran 500 tests ... OK (skipped=13)   # unchanged by the second read's push; re-run at 4c87d92ec
$ python3 scripts/validate.py ; python3 scripts/validate_convergence.py --all-recorded
exit 0 ; exit 0
$ python3 evidence/artifacts/token-stack-fresh-session-e2e-20261004/trust_readback_race.py [<tool at c8613fe16>]
tool at c8613fe16: exit 0 "trusted 1 hook(s) ..." ; fixed tool: exit 3 "changed during the write ('echo different-hook' is modified)"
$ python3 evidence/artifacts/token-stack-fresh-session-e2e-20261004/rtk_behaviour_probe.py <outfile>
18 rewrite cases under two configurations, 12 compared commands on a 66-commit fixture; the host's `rtk gain` counter unchanged (scratch HOME)
$ python3 evidence/artifacts/token-stack-fresh-session-e2e-20261004/rtk_init_codex_probe.py <outfile>
13 steps in 4 scenarios (existing AGENTS.md, none, flag refusals, CODEX_HOME outside HOME)
$ python3 evidence/artifacts/token-stack-fresh-session-e2e-20261004/codex_hook_qual.py        # seven arms, 4 min (run before this delta read; unchanged)
every arm: exit codes equal to the shell's; R1 6/6, R2 6/6, B2 4/5, R2b 6/6 executed with rtk; R1u and R1p 0/6; live hooks.json unchanged
$ bash evidence/artifacts/token-stack-fresh-session-e2e-20261004/fresh_session_e2e.sh nativestack   # 30 s (run before this delta read; only its heading text changed)
8 of 8 user-scope MCP servers connected; PreToolUse:Bash rewrote git status and grep -rl
$ gitleaks git --log-opts="origin/main..HEAD"
no leaks found

Decision record

docs/decisions/2026-10-04-codex-rtk-hook-qualified.md: the decision, its authority (the user's "yes frictionless" of about 22:05Z, relayed by the command center and recorded as relayed, since the writing session did not hear it), the evidence, alternatives (keep the hold-out, copy the hook entry from a template, the interactive /hooks review, --dangerously-bypass-hook-trust, trust everything, an absolute path in the hook command) and what would overturn it. The delta reads changed decisions 1-3, the evidence table, the bypass alternative and the residuals, and name the AGENTS.md pointer as the user's confirm-point.

Host evidence

No files under evidence/hosts/ change. The qualification ran in scratch Codex homes; the live ~/.codex was neither written nor read for credentials.

Checklist

  • No new or changed GitHub Actions or workflows.
  • No secrets printed, logged or committed (gitleaks above); the receipt replaces home paths.
  • No new paid hosting, subscription or billing surface.
  • Peer-owned untracked files and worktrees preserved.

🤖 Generated with Claude Code

@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Oct 4, 2026
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/99-codex-rtk-hook-20261004 branch 2 times, most recently from e3ed1fe to 4c87d92 Compare October 5, 2026 00:24
Scout and others added 4 commits October 4, 2026 21:01
…ts decision record

RTK 0.51.0 documents `rtk init -g --codex` as the Codex integration (README L133, L460: a PreToolUse hook with updatedInput plus
AGENTS.md), and the user's directive of 2026-10-04 asks for the upstream install, while this catalog held the hook out at the 0.50.0
pin because it was not qualified. This commit carries what does not depend on the install plan's rows (PR 684):

- tools/adoption/codex_hook_trust.py: Codex skips a non-managed hook until its hash equals [hooks.state."<key>"].trusted_hash
  (openai/codex rust-v0.159.3 discovery.rs hook_hash L775, hook_trust_status L794-L815), and `rtk init -g --codex` cannot trust the
  hook it registers. The tool sends the edit the TUI's /hooks review sends (config/batchWrite of hooks.state, upsert, tui/src/hooks_rpc.rs
  write_hook_trusts L58-L91) for the user-layer hooks whose command equals one named with --command, and for no other hook; dry run by
  default, --apply backs up config.toml (0600), refuses while a codex process runs, sends the user layer's version as expectedVersion and
  reads the result back through hooks/list. 15 tests with a fake app-server; run for real against scratch homes (dry run, apply with
  backup, idempotent second run, exit 4 without a matching hook).
- evidence/artifacts/token-stack-fresh-session-e2e-20261004/: the two reproducers (fresh_session_e2e.sh, codex_hook_qual.py), a
  sanitized receipt and the NativeStack before snapshot. Qualification on codex-cli 0.159.3 and rtk 0.51.0, scratch homes, one run per
  arm: the hook appends one group and changes nothing else, fires, preserves exit status (identical to the shell's in all seven arms),
  blocks nothing, coexists with another PreToolUse hook, leaves a model-prefixed command alone, is skipped until trusted and fails open
  without rtk on PATH.
- docs/decisions/2026-10-04-codex-rtk-hook-qualified.md: the decision, its relayed authority, alternatives and overturn conditions.

The plan rows, the handbook and card text and the awareness block follow in the next commits, once PR 684 lands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 08603a2)
… the bypass-flag claim, retain the rtk measurements (delta read: P2, 2 x P3)

The cross-family read of c8613fe found a P2 and two P3s; each is fixed and checked on its merits.
- P2: the read-back searched for the command again, so a hook that vanished or changed during the write dropped out of the check and
  the tool printed "trusted N hook(s)" with exit 0. Reproduced on the real codex app-server 0.159.3 (trust_readback_race.py: the tool at
  c8613fe exit 0, now exit 3, "changed during the write ('echo different-hook' is modified)"). Every named hook is now looked up by
  key and must still be a user-layer command hook at the hash it had and trusted; a discovery error that was not there before the write
  fails too. Seven tests were written first (ten failures on the old tool); 22 pass.
- P3: `codex exec --help` of 0.159.3 lists --dangerously-bypass-hook-trust (shared_options.rs L61-L64, exec cli.rs L145 at rust-v0.159.3).
  The absence claim came from a head-truncated filter; corrected in the README and the decision record, and logged.
- P3: the rtk claims are measured by a retained probe (rtk_behaviour_probe.py, rtk-behaviour-probe.json), with upstream defaults and with
  the repository's five exclude_commands. It also showed that the earlier statement that rtk leaves git show, diff, jq and git branch
  alone was the host's configuration, not rtk's default, and that bare git log prints no notice; both corrected, with an anti-pattern row.
- Residuals recorded: the dry run makes no trust or config edit but is not read-only; 0.160.0 is checked from source only; the counter
  delta of the snapshot spans other sessions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 2a269e7)
…what rtk init writes to the Codex home (delta read job-060: P3)

The closing read of e3ed1fe closed the read-back race and accepted the probe evidence; one P3 was left and one gap of mine is added:
- README "compact git-log formats have no cap" overgeneralised: rtk 0.51.0 adds -50 to --oneline, --format and --pretty when no count is
  given (git_cmd.rs L1819-L1825 at e001f773). The probe's 16-commit fixture could not reach it; it is now 66 commits (50 lines against
  git's 66, 66 and 68). README, record and anti-pattern log say so.
- `rtk init -g --codex` is not hook-only: it also creates RTK.md, appends an inert @<home>/.codex/RTK.md pointer to the Codex AGENTS.md
  (an instruction-file edit) and rtk's history database, honours CODEX_HOME, is idempotent, is undone whole by --uninstall, and refuses
  --hook-only, --no-patch and --auto-patch. rtk_init_codex_probe.py and rtk-init-codex-probe.json retain the measurement; the README and
  the record list the edit among the points for the user to confirm.
Text and retained probe output only: no change to the trust tool or its tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 98ce955)
…ision record in manifests/evidence.json

Hot-file protocol (docs/lanes.md): evidence.json as on main 1de2a7f with the changed docs/harness-defaults.md and the new paths (the
decision record and the ten files of evidence/artifacts/token-stack-fresh-session-e2e-20261004/) registered with
host_receipts.register_file, then component_matrix.py --write and new_host_grand_list.py --write; validate.py and
validate_convergence.py --all-recorded exit 0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/99-codex-rtk-hook-20261004 branch from 4c87d92 to 558d4da Compare October 5, 2026 01:03
@seathatflowsinourveins

Copy link
Copy Markdown
Owner Author

Claude session native-agent-stack-5f: landing at head 558d4da896ad046fd694dc4c8009fb7b12d95f96. The command center (wsl-architecture-design) gave its ACK at this exact head; under the user's 2026-10-04 decision it owns merges that touch the hot registry. The read-only cross-family review (GPT-6.1 Sol max, the packaged Codex SDK worker) returned ACCEPT at 4c87d92ec1a5; this head is reached from it through recorded carry edges (equal owned patch-ids, or a cross-family delta read returning ACCEPT at the edge's target), with no P1 item.

Observed main e871259eaee105044c2036d1ee1dbe6e250ab223. coordination/merge_tree_landing_check.py (sha256 a11649fcf2e5844c…) exit 0:

main e871259eaee105044c2036d1ee1dbe6e250ab223 head 558d4da896ad046fd694dc4c8009fb7b12d95f96 base 1de2a7f15054f56a29afa94e9ead65415e108c9a merged-tree 0029732b44f1c4be8f0baee368a7c5ece689155c merge-tree-exit 0
ok   1: clean three-way merge
ok   2: merged-vs-main paths 15, outside PR-owned 0 []
ok   3: main drift 43 paths, overlap with PR-owned inputs (registry excluded) []
ok   4: registry foreign rows equal True, order preserved True, PR-owned rows 12, unowned top-level keys differing []
ok   5: merged files[] sorted by path with no duplicates: True (10034 rows)
LANDABLE
condition 5: merged files[] sorted and unique (tree 0029732b44f1c4be8f0baee368a7c5ece689155c)

Required checks at this head: 8 pass . Unresolved review threads: 0.

@seathatflowsinourveins
seathatflowsinourveins merged commit 2875145 into main Oct 5, 2026
25 checks passed
@seathatflowsinourveins

Copy link
Copy Markdown
Owner Author

Claude session native-agent-stack-5f: post-merge observation. Landed as 2875145812ebea1bb299ac15d1106261c679cca0 (parent e871259eaee105044c2036d1ee1dbe6e250ab223), tree 0029732b44f1c4be8f0baee368a7c5ece689155c. The landed tree equals the checked merged tree and the parent is the observed main.

@seathatflowsinourveins
seathatflowsinourveins deleted the claude/99-codex-rtk-hook-20261004 branch October 5, 2026 01:33
seathatflowsinourveins added a commit that referenced this pull request Oct 5, 2026
…e and retired on re-apply, removed Codex flags pruned, jCodeMunch at user scope (#699)

### Scope

- Based on main `287514581` (which holds the squash of #684 and #701). Restacked from the stack on `a3045d629`: the content commits cherry-pick without a conflict and the registry is redone last on each restack; main moves often, so the lander may find the hot file to redo once more. The command center decided that NativeStack follows NativeStack2604 on the user's clean-install directive (2026-10-04, quoted in the record). This PR does three things:
  1. holds the two token-lane carrier hooks out of the shared Claude settings template and the default install, and makes a settings re-apply retire the hook entries a host already has (the merge never dropped a base hook, so a template change alone would have left NativeStack's live SubagentStart carrier running);
  2. adds `tools/adoption/codex_config_prune.py`, which deletes the `[features]` keys that `codex features list` reports as `removed` (NativeStack's `plugin_hooks = true` today) through Codex's own config writer;
  3. registers jCodeMunch at user scope in both shared user templates (the user's directive of the same day; the Claude and Codex user-scope sets must mirror each other by this repository's own tests), with the measured cost kept visible in its record.
- Base commit: `287514581` (main after #701). Review history: five cross-family reads of the retirement matcher, closed by the command center's closing read (job-059) at `748cbe418`, whose content this head carries plus a docs-only commit that records the closing read's two residuals
- Lane: `lane:foundation`
- Owned paths touched: `adoption/templates/{claude.settings,codex.config,project.codex.config}.template.*`, `adoption/templates/codex.AGENTS.template.md`, `adoption/scaffold/AGENTS.md`, `adoption/mcp/claude-user.json`, `adoption/hooks/claude/{README.md,held-out-hook-entries.json}`, `tools/adoption/{install_claude_profile,apply_claude_settings,codex_config_prune}.py`, `adoption/new-wsl/**` (map, additions, generated Codex block), `adoption/bootstrap.md`, `docs/token-session-handbook.md`, `docs/decisions/2026-10-04-*`, the tests, and `manifests/evidence.json` (last commit, `registry:`).

### SOTA sources

- Claude Code hooks: https://code.claude.com/docs/en/hooks (command hooks, `SessionStart` and `SubagentStart`; read 2026-10-04). The carriers are our own adaptation, not an upstream feature.
- Codex removed features: `codex features list` of the installed binary, and openai/codex `rust-v0.160.0`, `codex-rs/features/src/lib.rs` L1474-L1479 (`plugin_hooks`: `Stage::Removed`, `default_enabled: false`). Writer: `config/batchWrite` of `codex app-server` with `expectedVersion`, as `tools/adoption/apply_codex_lane.py` already uses it (its rollback deletes keys with `value: null`).
- Codex MCP approval: openai/codex `rust-v0.157.1`, `codex-rs/codex-mcp/src/mcp/mod.rs` L89-L98 (an `approve` mode auto-approves even under `approval_policy = "never"`).
- jCodeMunch: jgravelle/jcodemunch-mcp 1.108.319 at `8f7b34abe16fb459e0bf1c04747d584216dfe32e`: `README.md` L119-L122 (`claude mcp add -s user jcodemunch jcodemunch-mcp`) and L225 (the anonymous savings counter and its default index path), `CONFIGURATION.md` L229-L233 and `SECURITY.md` L311 (`JCODEMUNCH_SHARE_SAVINGS=0`).
- Cost evidence: the frozen Harbor E2E of 2026-09-30 (Harbor 0.23.0, upstream installers verbatim, 36 SWE-bench Verified tasks, Sonnet 5.5 at medium effort): jcodemunch (init + hooks) 1.387 times the lean arm, 95% CI 1.230 to 1.553, Holm p = 0.0006. That arm ran `init`; the registration here does not, and was not measured.

### Evidence-class table

| Claim | Evidence class | Command / receipt |
| --- | --- | --- |
| Merging the new template into a copy of NativeStack's live `~/.claude/settings.json` removes exactly one hook, the SubagentStart carrier; a second merge changes nothing; `keep_held_out=True` keeps it | `local_integration` (real input, in-memory merge on this host, with the host's resolved home) | a one-off run at `176ee5a6c` (the same content as `7edb91e3e` here), not retained as a receipt; stated in `docs/decisions/2026-10-04-claude-template-holds-out-token-lane-carriers.md`, decision 3 |
| `codex_config_prune.py` on NativeStack's live config (dry run, real `codex features list`) names `features.plugin_hooks` and nothing else | `local_integration` (real input, no write) | `python3 tools/adoption/codex_config_prune.py` exit 0 |
| `--apply` against the real `codex app-server` (codex-cli 0.159.3) on a scratch copy of NativeStack's `config.toml`: exactly the `plugin_hooks = true` line was removed, read back through `config/read`, a 0600 backup was written, a second dry run found nothing, and the live file kept its hash | `local_integration` (real upstream binary, scratch copy, no write to the live host) | 2026-10-04 20:07Z; the process guard was pointed at an absent name (`--codex-process-name`) because five other codex processes were running, which the live apply must not do; recorded in the decision record, decision 5 |
| The dry run, the removed-name parsing and the `--apply` path with its refusals (backup then delete through the writer and read back; a running codex refuses before any write; a failed write is reported with the backup; a key the writer still reports is a failure; two backups in one second do not collide) | `synthetic` | 12 tests in `tests/test_codex_config_prune.py` with a fake codex and a fake app-server |
| Carrier retirement retires a hook only when its command equals, exactly (no trimming), a carrier command this repository shipped, as shipped or with the placeholder replaced by the text `render_config.py` writes there. The renderer substitutes `HOME` as written, so that text may be any spelling of the settings file's home (a trailing slash, a `.` or `..` segment, a doubled or leading double slash, a symlink alias): it counts when it is an absolute path with no control character, quote, `$`, backtick or backslash whose realpath is the home's, and the home is the resolved `<home>/.claude/settings.json` or nothing is retired. Every other hook is kept (five cross-family reads found a defect in each earlier matcher or in what it was given: any shell word naming the path; the first non-option word and shlex tokens; an anchored pattern, a recognised grammar; trimming, a fallback home and two sequential replaces; and, in the fifth, two ways a carrier was kept because the renderer writes the home as given) | `local_integration` | `SHIPPED_CARRIER_COMMANDS`, four fixed shapes with a hole where `HOME` is; `HeldOutHookTests` (53 tests; the fourth and fifth rounds' were written first: 78 failures and one error, then nine failures): the four shipped strings with their sources, every shipped string retired as shipped and as rendered, no whitespace around a command tolerated (nine paddings), a mutation test (6,908 single-character edits, none retired), the second read's 25 shapes and the third read's 30 all kept (the pattern matcher retired 26 of the 55), another host's home does not retire, the carriers rendered by the real `render_config.render_one` with seven spellings of one home retire, a carrier rendered for a symlink alias or for the real home retires through either path, spellings that name another directory or that the shell expands (`.../$X/..`, a substitution, a backtick, a backslash, a quote, a newline, a no-break space) are kept, a target that is not a home's `.claude/settings.json` retires nothing; the reviewer's own repro file for the fifth read's two families passes 6 of 6 against this head; the record counts the second to fifth repairs as extra rounds past the bounded loop, the fifth being the final one |
| On the rebased head, the 49 test modules that name a changed file ran 3,414 tests in 850 s: 3,137 passed, 266 skipped, 11 failed. Two failures were this PR's: the disposition for `codex:config_group:otel.metrics_exporter` cited `codex.config.template.toml:269`, which the jCodeMunch lines moved to 291 (fixed in `66ba4c45e`; `test_upstream_surface_watch` passes, 110 tests). The other nine (8 in `test_token_e2e_grader`, 1 in `test_windows_terminal_defaults`) are not caused by this PR | `local_integration` | locally authored tests, not upstream acceptance; run niced at `33b236ba1` (the head before the citation fix); the six related modules (apply_claude_settings, install_claude_profile, codex_config_prune, new_wsl_client_config, ecosystem_manifest, adoption_docs_consistency) ran 447 tests, all passing (5 skipped), at every round and on the rebased head |
| The full suite on macos-15 (10,900 tests) failed one test at the head before this one: `test_a_settings_file_names_its_own_home` compared the literal `/home/example` with the settings file's resolved home, and macOS resolves `/home` to `/System/Volumes/Data/home`; fixed in `b6a152785` by comparing with `resolve()` (the code is unchanged); the artifact held no other failure | `local_integration` | the `validate-macos` job's uploaded log `full-suite-macos.log` (1 failure, 1,412 skipped); the module passes (53 tests) after the fix |
| The nine failures are pre-existing | control | the same nine test names fail when `tests.test_token_e2e_grader`, `tests.test_windows_terminal_defaults` and `tests.test_upstream_surface_watch` run on clean main `43005a9f8` (a detached worktree): 776 tests, 9 failures, 6 skipped; the surface-watch module passes there |
| jCodeMunch's user-scope registration, its opt-out and the cost of the faithful-adoption arm | `source_review` | the pinned README and CONFIGURATION.md lines above; the frozen Harbor `RESULTS.md` |
| Applying any of this on NativeStack (settings re-apply, Codex prune `--apply` on the live file, `codex mcp add jcodemunch`) | not run | untested boundary: the host steps are in the record, the command center runs them after the merge, the prune in a window with no codex process |

### Local commands run

```
$ python3 tools/adoption/new_wsl_client_config.py --check
check passed (the record's counts: 393 pieces, 354 wired, 24 not wired, 15 authorization)
$ python3 -B -m unittest <49 modules that name a changed file>   # at 33b236b
Ran 3414 tests in 849.6s ... FAILED (failures=11, skipped=266)   # 2 fixed by be75911, the other nine are the control row's
$ python3 -B -m unittest tests.test_token_e2e_grader tests.test_windows_terminal_defaults tests.test_upstream_surface_watch   # clean main 43005a9
Ran 776 tests ... FAILED (failures=9, skipped=6)   # the same nine
$ python3 -m unittest tests.test_upstream_surface_watch   # at 2f85d97
Ran 110 tests ... OK (skipped=6)
$ python3 scripts/validate.py ; python3 scripts/validate_convergence.py --all-recorded
exit 0 ; exit 0
$ python3 scripts/build_new_wsl_handbook.py --check ; python3 evidence/artifacts/new-wsl-install-plan-20261002/check_plan.py
exit 0 ; OK: 80 rows: 56 installed (54 by the default run, 2 only when named), 3 measurement-only, 21 not installed
$ python3 tools/adoption/codex_config_prune.py --codex-home <scratch copy> --apply --codex-process-name <absent name>
pruned plugin_hooks ...; read back from config/read   # then a dry run: nothing to prune; live config.toml hash unchanged
$ gitleaks git --log-opts="origin/main..HEAD"
no leaks found
```

### Decision record

`docs/decisions/2026-10-04-claude-template-holds-out-token-lane-carriers.md` (decisions, the NativeStack host steps, alternatives, what would overturn each); addenda in `2026-10-04-new-wsl-clean-default-holdouts.md` and `2026-10-04-new-wsl-jcodemunch-user-scope.md`.

### Host evidence

No files under `evidence/hosts/` change. Applying the Codex AGENTS template's changed top-rule line (`jcodemunch` shares serena's lane) to `~/.codex/AGENTS.md` is an instruction-file edit that the command center applies after this PR's cross-family read; it reported the user's yes at 20:40Z.

### Checklist

- [x] No new or changed GitHub Actions or workflows.
- [x] No secrets printed, logged or committed (gitleaks above).
- [x] No new paid hosting, subscription or billing surface.
- [x] Peer-owned untracked files and worktrees preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
seathatflowsinourveins added a commit that referenced this pull request Oct 5, 2026
…(second of two; the user's relayed pointer yes) (#705)

### Scope

- Second of the two pull requests of `docs/decisions/2026-10-04-codex-rtk-hook-qualified.md` (the first, #701, landed as `287514581`; this branch was based on main `4c897418f`, after #699 and #704, and carries main `4af7417b7`, after #720, #712, #696 and #724, as a merge commit). The install plan's `command-output` row now installs RTK's Codex PreToolUse hook with upstream's own command and trusts it:
  1. two commands after the existing four: `rtk init -g --codex`, then `python3 tools/adoption/codex_hook_trust.py --command "rtk hook codex" --apply`;
  2. its post_install acceptance gains the two Global `[ok]` lines of `rtk init --show --codex` and `codex_hook_trust.py --check`; a new after_sign_in acceptance runs a fresh `codex exec` told to add no prefix and requires the first `rtk git status` command item to have `exit_code == 0` and `status == "completed"` (review thread 1; **UNRUN**: the model call needs the sign-in);
  3. the trust tool's execution-rule review and `--check`. Codex rewrites a call to `rtk <command>` before it matches execution rules on the command words, so a `forbidden` or `prompt` rule on a command rtk can rewrite no longer matches it, and the rewrite space is not finite (env prefixes, global options such as `git -C`, absolute paths, wrappers, PHP tool spellings, user filters and Unicode word boundaries all change what rtk rewrites, and the rewrite changes words after `rtk` too: `cat f` becomes `rtk read f`). Three GPT reads each found a bypass in an analysing version of the review, so the tool now **parses nothing** (the design 5f recommended after 705f): it reads each regular `*.rules` file of `<codex home>/rules` as bytes and accepts it only if (1) its sha256 is on `tools/adoption/exec_rules_reviewed.json`, an exact-bytes review for one rtk version (`rtk --version` must match the entry, `rtk config` must show `transparent_prefixes = []`, `rtk trust --list` must say `No trusted filters.`, `RTK_TRUST_PROJECT_FILTERS` must be unset, the user TOML filters file beside rtk's config must hold no filter; today one entry, #713's `hcom-deny.rules` at head `7fbe586f3`, sha256 `ac6254ee…`, after its repair round 2 changed comments and justification strings only), or (2) it holds nothing but allow rules in Codex's own format byte for byte (the lines `amend.rs` writes, hcom's own `hcom.rules` with its one comment line, empty lines), none containing the letters `rtk` anywhere (the rewrite inserts that word, as a token or inside a shell-script token, so such a rule matches the rewritten command and not the original: codex-cli 0.159.3 `execpolicy check` allows `rtk git push origin main` under `["rtk"]`, and the script token `FOO=1 rtk git push origin main` under `["/bin/bash", "-lc", ...]`, and gives no decision for the originals; 705g P1-1). Anything else is an exposure: `--apply` refuses with exit 2 before the app-server starts (so before any write, even for an already trusted hook), `--check` exits 6 for a trusted hook beside one (so the post_install acceptance catches a rule file added or edited after the grant), `--allow-exec-rules` accepts it. The listing is Codex's `collect_policy_files` mirrored fail-closed (only opening the directory may report it missing; each entry's type comes from its own `lstat`, so an entry that vanished raises; a symlink, directory or named pipe is not loaded and never opened). `--check` exits 0 only while every named hook is trusted and no rule file is exposed, 5 when a hook is not trusted, 4 when none matches, 6 when it is trusted beside an exposure; mutually exclusive with `--apply`, no edit, no backup. The plan's trust and check commands pass `--rtk "$e/bin/rtk"`. 81 unit tests (local stand-ins for the app-server and for `rtk --version`, `rtk config` and `rtk trust --list`), 63 mutants each failing one;
  4. text that follows the row: the plan README sentence, the token session handbook paragraph (Codex runs RTK through the hook, and the exclusions are what make it leave `git show REV:path`, `diff`, `jq` and `git branch` alone), a dated note on the RTK card (cards are dated editions), the F9 recipe sentence and its owner-list test (`command-output` joins the after-sign-in owners), the new-WSL profile (`adoption/new-wsl-profile.{json,md}`), the definitive manifest and the consensus record it copies, which no longer say that `rtk init --global --codex` is never run (review thread 2; the manifest's declared consensus digest follows), the regenerated new-WSL handbook and its receipt's digests (the `wsl-new-distro-image` convergence record needs no re-pin: #704 freezes copies of the recipe and its test); the F9 owner list is #704's, with `command-output` added;
  5. the decision record's addendum, which quotes the user's answer **as relayed by the command center**: "yes for rtk pointer" (00:49:16Z in the command center's transcript, part of a longer message), and a standing rule,  "for the practice like rtk, always proceed with sota convergence highest quality practice with upstream repos" (00:50:08Z, a queued message; the command center first relayed both as "about 00:55Z", an estimate, and corrected the times). The writing session heard neither; both rest on that relay, which the user may withdraw. The answer covers what `rtk init -g --codex` does beyond the hook (RTK.md and an inert `@<Codex home>/RTK.md` pointer line in the Codex `AGENTS.md`; there is no hook-only mode with `--codex`), and a second addendum answering review thread 3: Codex rewrites the call before approval and matches execution rules on the rewritten words, rtk rewrites 36 of 55 measured commands including mutating ones, and rtk has no allow-list, so the trust step is guarded instead of limited (sources at both Codex pins and rtk 0.51.0, alternatives and the untested boundary in the record).
- Base commit: `4af7417b7` (main after #720, #712, #696 and #724; the branch's own base was `4c897418f`)
- Lane: `lane:foundation`
- Owned paths touched: `evidence/artifacts/new-wsl-install-plan-20261002/{install-plan.json,install.sh,accept.sh,README.md}`, `evidence/artifacts/token-stack-fresh-session-e2e-20261004/**` (README, the scratch-run script and its output, `fixtures/hcom-deny.rules`, the mutation check, the heads derivation, its binary check and the heads file `rtk-rewrite-heads.json`, now review evidence beside them), `tools/adoption/exec_rules_reviewed.json`, `evidence/artifacts/new-wsl-handbook-20261001/receipt.json`, `docs/{token-session-handbook.md,token-efficiency-stack.json,new-wsl-handbook.json,new-wsl-handbook.md}`, `adoption/new-wsl-profile.{json,md}`, `evidence/artifacts/new-wsl-definitive-defaults-20261001/definitive-manifest.json`, `evidence/artifacts/new-wsl-layer-consensus-20261002/consensus.json`, `docs/decisions/2026-10-04-codex-rtk-hook-qualified.md`, `adoption/platforms/linux-wsl2-new-distro.md`, `tools/adoption/codex_hook_trust.py`, the tests, and `manifests/evidence.json` (last commit, `registry:`). Nothing is installed or trusted on any host by this change.

### SOTA sources

- RTK: rtk-ai/rtk v0.51.0 at `e001f773f80b22b7dc4c7a79521b30e35aaef026`, `README.md` L133 (`rtk init -g --codex`), the installed `rtk init --help` (`--codex`: "uses PreToolUse hook + AGENTS.md + RTK.md"; `--hook-only`, `--no-patch` and `--auto-patch` refused with it), `rtk init --show --codex` and `rtk-init-codex-probe.json`.
- Codex hook trust: openai/codex rust-v0.159.3, commit `01fc69f4026735edfdf6789820549727a4867b11`, `codex-rs/tui/src/hooks_rpc.rs` L58-L91 (`write_hook_trusts`: the `/hooks` review persists trust with `config/batchWrite` of `hooks.state`) and `codex-rs/hooks/src/engine/discovery.rs` L775-L815 (hash and status), which `codex_hook_trust.py` reproduces.
- Codex hook dispatch and approval (review thread 3), identical lines at rust-v0.159.3 `01fc69f40267` and rust-v0.160.0 `a956835d0207`: `codex-rs/core/src/tools/registry.rs` L603-L660 (PreToolUse hook, `invocation = updated_invocation` L632, handler L689), `codex-rs/core/src/exec_policy.rs` L327-L420 and L770-L850 (rules matched on the command words; unmatched fallback), `codex-rs/shell-command/src/command_safety/is_dangerous_command.rs` L133-L143 (forced `rm`, unwrapping `sudo` and `env`), `codex-rs/hooks/src/events/pre_tool_use.rs` L357 and L388-L405, and `codex-rs/exec/src/exec_events.rs` L151-L166 (`CommandExecutionItem`: `exit_code`, `status`); `codex execpolicy check`, Codex's own rule evaluator.
- Execution-rule review, rust-v0.160.0 `a956835d0207`: `codex-rs/core/src/exec_policy.rs` L1121-L1170 (`collect_policy_files`: only opening the directory may report it missing, errors on the iteration and on an entry's file type propagate, every entry's type is asked before its extension, symlinks are not loaded), L645-L660 (`load_exec_policy_with_warning`: a parse error in any rules file makes Codex load no file rules), `codex-rs/execpolicy/src/amend.rs` (`blocking_append_allow_prefix_rule`: the format of the allow-only `default.rules`), `codex-rs/execpolicy/README.md` (`prefix_rule` and `host_executable` builtins, strictest decision wins). The 705f counterexamples rest on: `codex-rs/execpolicy/src/parser.rs` L64 and facebook/starlark-rust `starlark_syntax/src/lexer.rs` L415 at `0c8d107ab963` (a raw CR inside a string is dropped), `codex-rs/shell-command/src/bash.rs` L492, `codex-rs/core/src/exec_policy.rs` L876 and `codex-rs/core/src/shell.rs` L22 (assignment-prefixed scripts fall back to the whole shell argv); rtk v0.51.0 `e001f773f80b`: `src/discover/registry.rs` L393-L418 and L1811 (PHP tool normalization), `src/core/toml_filter.rs` L199-L227 and L456-L495 (filters load only while trusted; trusted user filters keep the complete pattern, alternative branches allowed), `src/hooks/trust.rs` L51-L166 and `run_trust` (the global trust store, `rtk trust --list`, and the `RTK_TRUST_PROJECT_FILTERS` + CI override), `src/discover/registry.rs` L1743-L1763 (`exclude_commands` is applied before the TOML-filter rewrite), `src/filters/gcc.toml` L3 and the rust-lang/regex word-class contract (Unicode `\b`), `src/discover/rules.rs` (94 `RULES` patterns, each `^`-anchored), `src/discover/registry.rs` L64 (`ENV_PREFIX`), L194-L215 (`classify_command` normalization), L438 and L568 (global options, absolute paths), L1386-L1498 (`PROCESS_WRAPPERS`, shell keywords, `uv run`), L1743-L1760 (the TOML filters are a second rewrite source), `src/filters/*.toml` (62 builtin filters), `src/core/config.rs` L119-L145 (`transparent_prefixes`), `src/main.rs` L3072-L3100 (`rtk hook check`: exit 0 with the rewrite, exit 1 with `No rewrite for:`) and `rtk config`'s output (`show_config`). hcom's own rule file (aannoo/hcom `7151660a3`): `src/hooks/codex.rs` `build_codex_rules` L1508-L1534 and `HCOM_TOOL_NAMES` L578-L585, `src/hooks/common.rs` `SAFE_HCOM_COMMANDS` L52-L72.
- rtk v0.51.0 `e001f773f80b`: `hooks/codex/README.md` L14-L24 (the rewrite precedes Codex's approval and sandbox checks), `src/hooks/permissions.rs` L64-L72 (no Codex rule source), `src/core/config.rs` L119-L123 and `src/discover/registry.rs` L1548-L1582 (`exclude_commands` is exclusion-only), `rtk hook check --agent codex`.
- Codex expands no `@` reference in `AGENTS.md`: `docs/token-efficiency-stack.md`, citing `codex-rs/codex-home/src/instructions/mod.rs` at rust-v0.157.1.
- The plan's own conventions: `check_plan.py` (commands, acceptance and scripts agree), the hot-file protocol of `docs/lanes.md`, and #694's re-pin of the recipe-freezing convergence record.

### Evidence-class table

| Claim | Evidence class | Command / receipt |
| --- | --- | --- |
| The row's steps, read from `install-plan.json` and run in scratch HOMEs with rtk 0.51.0 and codex app-server 0.159.3: `--check` exit 4 before the hook exists; `rtk init -g --codex` 0 (the hook lands at `pre_tool_use:1:0`, after a memory hook's group); `--check` 5 before the trust; the trust 0 ("read back from hooks/list by key and hash"); the post_install program 0; and nineteen homes for the rule review: #713's `hcom-deny.rules` by its exact bytes lets the trust proceed, and a `git push` rule added after the grant makes `--check` exit 6 and the trust refuse; the same bytes plus one comment line, a configured rtk transparent prefix, a user TOML filter beside the reviewed file, a trusted project filter and the CI override variable `RTK_TRUST_PROJECT_FILTERS` (the last two each make the real rtk rewrite `hcom kill luna`, recorded before and after, and with rtk exclusions for `hcom` and `uvx hcom` in its config the same filters leave both commands alone) are each refused; Codex's `default.rules` format and hcom's `hcom.rules` format let the trust proceed; a `git push` forbid rule is refused (exit 2, nothing written; `--check` 5), accepted with `--allow-exec-rules`, after which `--check` exits 6; the same rule with its `rtk git push` twin is still refused; the counterexamples of 705e (`git -C .`, broad `uv` and `npx`, the nested `host_executable` file, under which the real codex evaluator forbids `git push origin main`) and of 705f (a raw CR in a token, a `bash -lc` script rule, `phpunit.exe`, `g++` plus a combining mark, each of which the real rtk rewrites or the real evaluator reads differently), an allow rule that holds `rtk` (the evaluator allows `rtk git push origin main` under `["rtk"]` and the script token `FOO=1 rtk git push origin main` under a `bash -lc` rule, and gives no decision for the originals; the real hook produces the second from `FOO=1 git push origin main`) and an unreadable rules directory are refused (79 steps, all as expected) | `local_integration`: one run, one host | `plan_row_scratch_run.py`; `plan-row-scratch-run.json` (the trust command carries `--codex-process-name no-such-process-name` because other codex processes ran on the host; recorded in the file) |
| The after_sign_in filter and test, taken from the plan's own text, over six streams in the shape of Codex's JSONL command item: the good one (`rtk git status`, exit 0, completed) ends 0, and five negative fixtures (exit 127, completed with exit 1, declined, no rtk prefix, no command item) end 1 | `synthetic` | same file, the six after_sign_in steps |
| The after_sign_in check with a real fresh `codex exec` and a signed-in launcher | **not run** | untested boundary: the host step after the sign-in; the earlier arms R1 and R2 of `codex_hook_qual.py` showed the same prefix through the gateway route |
| `--check` and the review, with local stand-ins for the app-server and for `rtk --version` and `rtk config` (`FakeRtk`): `--check` 0 when trusted and no rule file is exposed, 5 untrusted or modified, 4 none matches, 6 trusted beside an exposure; no edit and no backup; `--apply` refused with exit 2 before the app-server starts (no `AppServer` call, no backup, no write) for every counterexample above, a reviewed file with one changed byte, another rtk version, a failing rtk, config or trust-list command, a configured prefix, a trusted filter, the override variable or a user filter, a missing or malformed reviewed list, an unreadable file, and 29 near-misses of the allow-only grammar (a prompt line, CRLF, a tab, an escape, a non-ASCII byte, a justification, alternatives, a second statement, ...); accepted: the reviewed bytes under another name, Codex's and hcom's allow-only files (hcom's rebuilt from its installer's source lists), empty and comment-only files, tokens that merely contain the letters `rtk`; a listing in which an entry vanished, an iteration or an open raised, or a path that is a file is refused, while a symlink, directory or named pipe is not loaded (the pipe is never opened) | `synthetic` (fake app-server, fake rtk, fake `scandir`) | `tests/test_codex_hook_trust.py` (81 tests) |
| The reviewed entry rests on the real rtk 0.51.0: the derived heads (`rtk-rewrite-heads.json`, 142 from the 94 RULES patterns and 62 builtin TOML filters, the wrappers and the env prefix) agree with the binary: a positive control for each of the 94 patterns, 13,041 scanned commands (1,449 names, 9 argument shapes) with 623 rewrites and none outside the heads, 67 wrapper prefixes in front of `git status` (24 rewritten, all led by a head), 8,884 spellings of the 101 literal heads with 480 rewrites and none outside the heads; and the entry's own first tokens `hcom` and `uvx` in 22 spellings and 8 shapes (352 commands) are never rewritten. The derivation had 101 gaps in the first scan (the `r#"..."#` sbt rule, the TOML filters, `\b` continuations such as `gcc-13`), now 0. Review evidence, not a gate: the tool reads neither file | `local_integration` (the scan can falsify the review, not prove it) | `derive_rtk_rewrite_heads.py`, `check_rtk_rewrite_heads.py`; `rtk-rewrite-heads-check.json` |
| The tests discriminate: 63 mutants (listing: a `FileNotFoundError` on an entry or the iteration or an open error read as no rules, the extension before the file type, a followed symlink, a named pipe loaded, any name ending in `rules`, an unsorted listing; grammar: a prompt rule, a quote, a backslash, a tab or CR in a token or comment, trailing text, `any` for `all`, no empty lines, any comment, spacing, an empty pattern, an allow rule that holds `rtk` in four variants (a token after a wrapper, whole-token only, inside a shell-script token, only a leading token); the reviewed list: a malformed, uppercase or over-long hash, a malformed list read as empty or not blocking, the list read with no rule file; the decision: every file reviewed, every file allow-only, an unreadable file not blocking, rtk asked for allow-only files; rtk conditions: the version gate, exit statuses, the `Config:` line, transparent prefixes, the trust list (ignored, exit status ignored, matched loosely), the override variable (ignored, only the value 1), user filters, the schema line, comments, an unreadable or non-UTF-8 filters file, no rtk, no PATH lookup, telemetry, a terminal, no time limit; the flow: `--apply` not refusing, the flag accepting nothing or not accepting, `--check` ignoring an exposure or answering 6 before 5, `--rtk` not passed, no dry-run note) each fail at least one test; the tests were written after the code, so this is the check that they are not vacuous | `local_integration` | `rule_review_mutation_check.py`; `rule-review-mutation-check.txt` |
| Codex applies the hook's rewrite before approval and matches execution rules on the rewritten words: with a rule forbidding `git push` and one prompting `git commit`, `git push` is `forbidden` and `git commit` is `prompt`, while `rtk git push` and `rtk git commit` get no decision (each answer needs exit 0 and a valid response) | `local_integration` (Codex's own evaluator, codex-cli 0.159.3) and `source_review` for the dispatch order at both pins | `plan-row-scratch-run.json`, `execpolicy_check`; the decision record's addendum |
| rtk 0.51.0 rewrites mutating commands: 36 of 55 measured, among them `git push`, `git commit`, `gh pr merge`, `docker run`, `pip install`, `rsync --delete`; 19 are not (`git reset --hard`, `rm -rf`, `npm publish`, `terraform apply` and others); the five `exclude_commands` change none of these | `local_integration`: one run, one host | `rtk_behaviour_probe.py`; `rtk-behaviour-probe.json`, `codex_hook_check` |
| A live Codex session with a forbidding execution rule and the hook active | **not run** | untested boundary: derived from the dispatch order and the evaluator above; project and managed rule layers are not visible to the tool; `RTK_TRUST_PROJECT_FILTERS` is checked in the tool's own environment, not in the one Codex runs the hook in, and a filter trusted after the review shows at the next `--check` (705g P1-2: a point-in-time check cannot establish what a global hook does in every working directory; the exclusions of alternative (j) are the enforced hardening and are not enforced here); the reviewed list covers rtk's default hook configuration of one version; hcom's installer itself was not run (its file is rebuilt from its source) |
| `install-plan.json`, `install.sh` and `accept.sh` agree | `local_integration` | `check_plan.py`: 80 rows, 163 commands, 101 acceptance entries (with #704's rows) |
| The generated handbook and its receipt follow the edited recipe, and the convergence record stays valid without a re-pin | `local_integration` | `build_new_wsl_handbook.py --check` 0; `test_new_wsl_handbook` OK; `validate_convergence.py --all-recorded` 0 |
| The user's yes for the pointer and the standing rule | relayed, not heard | the decision record's addendum |

### Local commands run

```
$ python3 scripts/validate.py ; python3 scripts/validate_convergence.py --all-recorded
exit 0 ; exit 0
$ python3 -B evidence/artifacts/new-wsl-install-plan-20261002/check_plan.py
OK: 80 rows ... 163 commands and 101 acceptance entries agree with the scripts
$ python3 -B scripts/build_new_wsl_handbook.py --check
exit 0
$ python3 -B -m unittest <24 modules that name a changed file>
Ran 1028 tests ... OK (skipped=2) (17 modules that name a changed file, on the merged head; 1522 tests in 24 modules before the merge)
$ python3 evidence/artifacts/token-stack-fresh-session-e2e-20261004/plan_row_scratch_run.py <outfile>
79 steps, all as expected
$ python3 evidence/artifacts/token-stack-fresh-session-e2e-20261004/rule_review_mutation_check.py
63 mutants, survivors: []
$ gitleaks detect --log-opts="4c897418f..HEAD"
no leaks found
```

### Decision record

`docs/decisions/2026-10-04-codex-rtk-hook-qualified.md` gains the 2026-10-05 addendum (the relayed answer, its effect, and the one-command undo: `rtk init -g --codex --uninstall` removes the hook, `RTK.md` and the pointer together), its Residuals sentence now says the user approved the pointer edit, and a second addendum, "approval and execution rules": five sourced findings (item 5 corrected: approvals off do not switch execution rules off, and NativeStack2604 has no user-layer `rules` directory), the decision (the hash-list rule above, why three analysing versions failed, the reviewed list and its conditions, **the pin-move rule: a move of the rtk pin re-reviews the list**, tied to the RTK row of `docs/token-efficiency-stack.json`), the evidence, the limits and what would overturn it, the review rounds, and ten alternatives (an `exclude_commands` read-only limit, whose probe shows `git -C /tmp push` still rewritten; a never-rewrites-mutating proof; holding the hook out; shipping `rtk` rules; refusing on any rules file; the probe-and-sample review; a wrapper hook; derived heads with a parser; a purpose-written parser; (j) enforced `exclude_commands` entries for `hcom` and `uvx hcom`, measured and not enforced, with why).

### Review threads

Three Codex-connector threads, each answered with its fix and source in the thread and resolved at head 4739ea8: after_sign_in exit status (`accept.sh`), the profile's "never runs `rtk init`" statement (`install.sh` L446), and approval classification against the rewrite (`install.sh` L448).

Cross-family reads of the guard, four versions: 4739ea8 (refuse on any rules file) was read by 705d (Sol, through 5f: P1 a failed listing of the rules directory read as no rules, P2 the refusal came after the app-server started, P2 the scratch run accepted a failed evaluator) and job-071 (through the command center: P2 the same listing hole, P2 rules reported only for a hook still to be trusted, P3 the claim that approvals-off hosts consult no rules). `e6321c0b8` (a behavioural review with `rtk hook check` and `codex execpolicy check`) was read by 705e: three P1s that sampling cannot close (an entry-level `FileNotFoundError`, a `prefix_rule` nested in a `host_executable` argument, extensions of a prefix rtk leaves alone). `c771da9eb` (heads derived from rtk's source and a literal-only `ast` reader) was read by 705f: five P1s (a raw CR in a Starlark string, a rule on the whole shell argv, PHP tool suffix spellings, user filter regexes with a second branch, Unicode word boundaries) and a P2 (`DirEntry.is_file()` swallows a missing entry). `3a2bb97ba` (content `2d052a659`) replaced the analysis with the hash list on 5f's recommendation and added one exclusion found while checking the design against the real evaluator: an allow rule that names `rtk`. 705g (Sol, through 5f, at `3a2bb97ba`) found two P1s and no P2 in it: (P1-1) the exclusion refused only a whole token `rtk`, but Codex evaluates an assignment-prefixed script as one token of the shell argv and the hook inserts `rtk` inside it, so `["/bin/bash", "-lc", "FOO=1 rtk git push origin main"]` allowed the rewritten script and not the original; (P1-2) project filters, which rtk loads from the hook's working directory and which `rtk trust --yes` or `RTK_TRUST_PROJECT_FILTERS=1` with a CI variable activate, rewrote `hcom kill luna` out of the reviewed forbid rules while the reviewed hash still passed. This head's repair (`b7f758768`, registry `1b8d4c074`; merge of main `9080bc324`) repairs both in one bounded round: `rtk` is refused anywhere in an allow rule (regression cases, a whole-token mutant, a scratch home); the reviewed-file conditions also need an empty `rtk trust --list` and no `RTK_TRUST_PROJECT_FILTERS` (the reviewer's second option: classify the exposure so that it needs `--allow-exec-rules`), with rtk's exclusions measured as the enforced alternative and recorded, not enforced. It also follows #713's repair round 2 (the entry holds `ac6254ee…`, the 7fbe586 bytes; only comments and justification strings differ from 5f295f2).

705h (Sol, through 5f, at `14a8b119f`; verdict `verdict-705h.json`) **ACCEPTed**: no P1 and no P2. Both 705g P1s were verified fixed against the real binaries (a lowercase `rtk` anywhere in an allow line is refused, and the whole-token mutant dies with 36 test failures; the trust list and the override variable classify both project-filter activation routes, `--apply` exit 2 and `--check` exit 6, with the default and the XDG layouts, and `rtk trust --list` detected the filter from another directory), and no additional bypass was found in the pinned rewrite paths and inputs. Two boundaries stay not verified and are recorded as residuals: runtime coverage (codex-cli 0.159.3 and rtk 0.51.0 were probed, rust-v0.160.0 was read from source, no fresh model session) and the point-in-time and environment limits (the classification observes the current user-layer rule bytes and the tool process's RTK environment and trust store; no atomic snapshot across listing, loading and the grant, and no enforcement after a later change of rules, filters, HOME or XDG paths or the hook's environment; project and managed Codex policies are outside its scope).

### Merge of main

`9080bc324` merges main `4af7417b7` (#720 re-pin to v2026.10.05.1, #712, #696, #724) into the branch. Conflicts were only in the generated and hot files (`docs/new-wsl-handbook.md`, the handbook receipt, `manifests/evidence.json`), resolved by the hot-file protocol of `docs/lanes.md`: main's copies, the branch's 44 registry entries re-registered from the pre-merge snapshot, the handbook regenerated with main's generator (#724), the receipt digests refreshed. Carry check: of every file this PR changes against its old base, only `docs/new-wsl-handbook.{json,md}`, the receipt and `manifests/evidence.json` differ between the pre-merge head `1b8d4c074` and the merge head (`git diff 1b8d4c0 <merge head> -- <the PR's files>`); the 705g repairs are `3a2bb97ba..1b8d4c0`. The merged tree failed two tests of main's new rules, fixed in `a7990f56a` (registry `14a8b119f`): the F9 unit says `tools/adoption/exec_rules_reviewed.json` was added after `v2026.10.05.1` (main's unreleased-path marker rule, #720), and the receipt's frozen profile digest is the branch's (the merge took main's receipt); the handbook and the receipt follow.

### Host evidence

No files under `evidence/hosts/` change. The scratch run used a scratch HOME; the live `~/.codex` and rtk configuration were neither read nor written.

### Checklist

- [x] No new or changed GitHub Actions or workflows.
- [x] No secrets printed, logged or committed (gitleaks above).
- [x] No new paid hosting, subscription or billing surface.
- [x] Peer-owned untracked files and worktrees preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
seathatflowsinourveins pushed a commit that referenced this pull request Oct 5, 2026
… the landed pull requests

PR 684 landed as ba1f687, the first pull request as #701 and the second as #705 (0ce9536); the original status text stays, labelled as the status of 2026-10-04 (5f's #747 read, one P2).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Oct 5, 2026
…es and the refresh that landed (#747)

### Scope

- What this PR changes, in one or two sentences: records in the Codex rtk hook decision record that 705h (Sol, through 5f) accepted the guard at `14a8b119f` with no P1 or P2, adds its two unverified boundaries to the residuals (no atomic snapshot across listing, loading and the trust grant; no enforcement after a later change of the rules, filters, HOME or XDG paths or the hook's environment) and notes that the head that landed (`118f579ff`) differs from the accepted one only in the regenerated new-WSL handbook outputs and their receipt. It also qualifies the record's opening Status paragraph (written 2026-10-04) with the landed pull requests: PR 684 `ba1f6876c`, the first of the two #701 `287514581`, the second #705 `0ce953696` (5f's read of `79622e8e7`, one P2). Record-only; no code, plan or generated file changes.
- Base commit: `0ce953696`
- Lane: `lane:foundation` (the same lane as #705)
- Owned paths touched: `docs/decisions/2026-10-04-codex-rtk-hook-qualified.md` and its registry row in `manifests/evidence.json` (last commit, hot-file protocol).

### SOTA sources

- No new source: a record-only follow-up to #705, which cites its sources in full. The facts added here rest on the same ones: rtk-ai/rtk v0.51.0 (`e001f773f80b22b7dc4c7a79521b30e35aaef026`); openai/codex rust-v0.159.3 (`01fc69f4026735edfdf6789820549727a4867b11`: `codex-rs/tui/src/hooks_rpc.rs` L58-L91, trust written by `config/batchWrite`) and rust-v0.160.0 (`a956835d0207`: `codex-rs/core/src/exec_policy.rs` L1121-L1170, the rules read when a session loads them).
- The 705h read is our own cross-family review of the integration (Sol through 5f), recorded as a review, not as an upstream source.

### Evidence-class table

| Claim | Evidence class | Command / receipt |
| --- | --- | --- |
| 705h accepted `14a8b119f`: no P1, no P2, both 705g P1s fixed against the real binaries, two boundaries not verified | `source_review` (cross-family read; verdict held by 5f) | the decision record's review-rounds paragraph |
| 28 of the 31 owned files are patch-id-equal between the accepted head and the landed head; the other three are regenerated handbook outputs and their receipt | `local_integration` (`git diff -U0` per file, `git patch-id --stable`) | computed on the #705 branch before the merge; reproducible from `14a8b119f` against its base and `118f579ff` against `cb339488e` |
| The probes ran codex-cli 0.159.3 and rtk 0.51.0; rust-v0.160.0 was read from source; no fresh model session | `local_integration` and `source_review` | already recorded by #705; restated as the boundary |

### Local commands run

```
$ python3 scripts/validate.py
{"components": 69, "hashed_files": 10169, "profiles": 4, "receipts": 204, "status": "passed"}
$ python3 scripts/validate_convergence.py --all-recorded
record[30]: valid
$ python3 -m unittest tests.test_component_matrix tests.test_evidence_manifest tests.test_new_host_grand_list tests.test_validate tests.test_codex_hook_trust
Ran 253 tests ... OK
(no test module names the decision record; gitleaks on each commit: no leaks found)
```

### Decision record

`docs/decisions/2026-10-04-codex-rtk-hook-qualified.md` (the residuals and review-rounds paragraphs).

### Host evidence

Not applicable (no files under `evidence/hosts/`).

### Checklist

- [x] New/changed GitHub Actions are pinned to a full commit SHA with a version comment (no workflow changed).
- [x] New/changed workflows declare top-level `permissions: {}` (no workflow changed).
- [x] No secrets are printed, logged or committed; no new required secret was added.
- [x] No new paid hosting, subscription or billing surface was introduced.
- [x] Peer-owned untracked files and worktrees were preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant