Repository navigation
Detect drift between the installed Claude agent definitions and the repository's (CPA-V-10) - #1062
Merged
seathatflowsinourveins merged 10 commits intoOct 13, 2026
Conversation
seathatflowsinourveins
force-pushed
the
claude/cc-native-practice-agent-drift-check-20261013
branch
2 times, most recently
from
October 13, 2026 12:17
2723f8f to
b3da64d
Compare
…epository's: adoption_status --client-wiring counts them and the wiring rule requires zero CPA-V-10 of the Claude-family practice audit. The client serves the definitions in ~/.claude/agents to every project that does not define the name itself; tools/adoption/install_claude_profile.py copies the 11 of adoption/agents/claude there verbatim, and nothing compared them afterwards (the audit found five of 11 host copies stale; scripts/adoption_status.py had no reference to the Claude agent paths). A stale copy is old text; a wider one is a different kind of risk, since an omitted tools field means every tool (anthropics/claude-code@3deb87368737 plugins/plugin-dev/skills/agent-development/SKILL.md L152-L154). - scripts/adoption_status.py: claude.stack_agents_drift, a count that is 0 when every *.md of adoption/agents/claude is installed byte for byte under <Claude home>/agents and nothing else is there but the four vendor-installed search definitions (codebase-memory, codebase-memory-auditor, codebase-memory-scout, semble-search: listed, not counted). A copy that is absent, a link, a folder or another kind of file counts (a link's target is not what the client was shown); a *.md not shipped here and not listed counts; backups, other extensions and anything below the top of the folder do not. null (the comparison cannot be made: the source folder has no definition or cannot be read, the agents path is a link or not a folder, an installed copy cannot be read) makes complete false like every other null. A non-zero count makes complete false: the fail-first of the audit item, a fixture with one stale user agent that reported complete: true. - docs/token-efficiency-stack.md: the coverage check and its rule; docs/decisions/2026-10-13-claude-agent-drift-check.md: the decision, the evidence, what is not covered; evidence/artifacts/agent-drift-check-20261013: the receipts (the run on the first host: 0 and complete true; the fail-before; the mutants). Tests: 12 new ClientWiringTests and the wired, unwired and wrong-type fixtures updated (153 tests in the module). 16 of them fail on the script at c63c83a (constants aside), among them the stale-agent fixture. 20 of 20 mutants killed. The practice page note (reference/workers.md L13, "four host copies differ") is stale; it is corrected with the next re-pin of the practice skill, which pins the page. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… through descriptors and at any depth, a vendor file that declares a shipped name is drift, and the catalog citation is repaired Astra's first read of e70d2de (CHANGES_REQUESTED: one P1, one P2, three follow-ups) and the command center's Claude read of the same head (one P1, one P2, four P3), folded into one commit. - P1 (Astra): the check inspected a path with lstat and opened it again, so a shipped file, or the whole agents folder, replaced by a link in between was read through (outside bytes equal to the source: drift 0 and complete true). The Claude home is opened, then agents with O_DIRECTORY|O_NOFOLLOW, then every entry with O_NOFOLLOW relative to the descriptor of its folder, and what is described is fstat of the descriptor that was opened. There is no fallback to a path check: a platform without O_NOFOLLOW, O_DIRECTORY or descriptor-relative open, stat and listdir reads nothing (null). The shared reader read_regular_bytes and its Codex caller stack_roles_matching read the same way, as the read asked. python/cpython@cbc944f4bc59 Modules/_io/fileio.c L404, Doc/library/os.rst L1272, L1364-L1365, L3507, L3551. - P1 (Claude read): the client loads the folder recursively (https://code.claude.com/docs/en/sub-agents.md L176-L182; the installed 2.1.296 loader enters every folder, follows links and lists hidden files), so a nested definition read as no drift. Every *.md at any depth is counted, a link, pipe or socket named *.md included; a link that leads to a folder, an entry that vanishes while it is read, a tree deeper than 8 folders or with more than 5,000 entries are null. - P3 (Claude read): a vendor-named file whose front matter declares the name of a shipped definition is counted (one name, one definition wins); a vendor name below the top, or as a link, is not exempt. - P2 (Astra): the inserted lines moved the source cited for codex:host:RTK.md in the catalog, and two citation assertions failed; the citation is scripts/adoption_status.py:532-553 (valid whether lines are counted by splitlines or by LF) and its registry binding is refreshed. - P2 and P3 (Claude read, Astra): the record cites its sources (documentation page, loader, CHANGELOG L8578 for CLAUDE_CONFIG_DIR and L7088 narrowed), its two wrong claims about links and nested files are corrected, it has a registry row, and its host command is as the receipt records it. Tests (170): 16 tests fail on the sources of e70d2de (FAILED failures=24, errors=3, counting subtests), among them the four replacement tests, which try a replacement after every file-system call of the read (the old check answered 0); 49 of 49 mutants killed. The installer's link handling, the non-default Claude home and the 18 hostname lines are follow-ups in the pull request body. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… as the client reads it or the count is unknown, the descriptor opens are accounted in the push gate's advisory inventory, and the record's counts are held to the receipts Astra's second read of e22358e (CHANGES_REQUESTED: two P2, five P3) and the command center's Claude read of the same head (one blocker, three P3), folded into one commit. - P2 (Astra): the descriptor opens added three reads to the OpenHands push gate's advisory inventory (9 to 12 for scripts/adoption_status.py), and tests.test_runtime_worker_openhands_push_gate failed two tests. A new dated monitoring delta (unclassified-gate-reads-20261013-pr1062-delta.json, derived with the native GitTree and derive_ci_protected, index entry appended, earlier snapshots and deltas unchanged, both registered) accounts for them: 347 reads against the 344 of the baseline it names; the three are the opens in open_below, stack_roles_matching and stack_agents_drift. - P2 (Astra): the vendor-name rule read the plain `name:` line only, so a vendor-named file that declared a shipped name through a quoted key, a block scalar, an escape sequence or a byte order mark gave 0 and complete wiring. agent_identity applies the client's block extraction and reads the plain spellings; every other spelling is unknown (the count null, complete false), and so is a shipped definition whose name it cannot read. Measured against the installed 2.1.296 client's own parser (its functions copied unchanged by byte interval and run on Bun 1.4.2; the client embeds 1.4.3): 0 misses over 8,582 generated texts and 26 real definitions, and over five further seeds of 34,556 texts. The first review's claim for the vendor rule is narrowed to what is measured. - Blocker (command center): the record quoted 46 of 46 killed for a receipt of 49. Each line that names a mutation receipt now carries that receipt's own count, and tests/test_agent_drift_record.py holds it. - P3 (command center): a pipe, a socket, a device and a link to nothing are not counted (the client's walker keeps regular files only); a *.md with no name stays counted, now said in the record and the docstring; a link is told by the ELOOP of the open and not by a later look; `name:` with its value on the next line is read. - P3 (Astra): the locator in tools/adoption/host-baseline.md follows launcher_resolution. Tests: 178 in the module; 9 of them fail on the sources of e22358e (failures=42, errors=62, counting subtests), the record test fails on its record, and the push-gate module's 127 tests pass here and fail two at e22358e; 92 of 92 mutants killed. The installer's link handling and non-default Claude home, the hostname lines of the touched files and the upstream captures are follow-ups in the pull request body. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… asked of the client's own YAML parser or the count is unknown The command center's Claude read of e7260fd (one P1, reproduced) and Astra's second delta (CHANGES_REQUESTED: one P2, the same case, and four P3) found that the hand-written name reader, rewritten in each of two rounds, still returned none for a name followed by a comment line at the left margin, so a vendor-named file declaring a shipped name gave drift 0 and complete wiring. The adjudication ruled a design round: parse the front matter with the client's own implementation, failing closed. - agent_front_matter extracts the block as the client's loader does (a byte order mark dropped, /^---\s*\n([\s\S]*?)---\s*\n?/); agent_names hands every block of the check to Bun.YAML.parse through bun at CLIENT_BUN_VERSION (1.4.3, the version the installed 2.1.296 client embeds) in one call, in an empty scratch directory with an empty configuration, that directory as home and a three-variable environment, the blocks as JSON on stdin. A name, none or a throw; a throw, no bun, another version, a bun that fails or does not answer in 30 s is unknown (the count null, complete false), never 0. agent_identity and its regexes are removed. - Measured against the installed client itself (each text written as a project agent, the client started in headless mode and killed after its init record): 16,428 generated texts, 0 misses with bun 1.4.3 and with 1.4.2; the reader of e7260fd missed 501, all of them comment lines between a key and its value and nine CRLF texts. The 2,987-text subset and the client's answers are committed (tests/fixtures/agent_frontmatter/client-oracle.json) and replayed by the tests. - The tests that need the real bun skip on a developer's machine and fail under GitHub Actions (the pattern of tests/test_token_full_save_evidence.py); validate.yml installs bun 1.4.3 from the release at its checksum. - P3: a test holds every test name that the record and the README cite; the README states the real definitions once; the oracle receipt names the client's whole parse path by function and byte interval and the record states Iwt's other refusals; the superseded oracle receipt is marked; the advisory delta of the design round states the edited read line; the hostname count is 57 occurrences on 55 lines. Tests: 203 in tests.test_adoption_status; on the sources of e7260fd the same file gives failures=1, errors=58 in the 196 tests that run there (counting subtests; 24 tests and the setup of the real-bun class raise AttributeError for the API the parser call adds; the 7 tests of that class cannot start); 38 of 38 mutants killed. Host step, not applied here: bun 1.4.3 (mise install [email protected], or AGENT_FRONTMATTER_BUN) on the first host, where mise has 1.4.2 and the count is null until then. The installer's link handling and non-default Claude home, the upstream captures and the hostname lines of the touched files are follow-ups in the pull request body. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…'s wording on a non-string name, and that mise install keeps the active version The command center's Claude delta of fb968af (PASS_WITH_P3) accepted two points on condition that the record say them: a successful parse that is not a mapping, or whose name is not a string, is none and not unknown, which departs on purpose from the adjudication's wording (item 2 asked for unknown) because the client's own loader serves a definition only under a string name (measured: the client served nothing in all 1,829 oracle texts the pipeline answered none for, and no text where it said none and the client served a name); and `mise install [email protected]` does not change the active version, so the check needs AGENT_FRONTMATTER_BUN (the shim in a scratch home resolves 1.4.2, measured by the command center). Record text only: no code, test or receipt changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…t's delimiter precheck, so an unterminated block of whitespace is read in linear time The local codex review of the rebased head 2723f8f said "patch is incorrect" (one P2) and the command center ruled one bounded, scope-locked round. A vendor-named file of "---" followed by many whitespace bytes and no closing line made the front matter pattern backtrack quadratically over the whitespace (64 KiB of newlines took 18.9 s, 32 Ki pairs of a space and a newline 10.7 s, a file may be 1 MiB), and the extraction runs before bun, so no timeout of bun bounds it. The design round had removed the client's check that "---" occurs after the first three characters as one that changes no result; it changes no result and it bounds the work. - scripts/adoption_status.py agent_front_matter: return none when "---" does not occur after the first three characters, the check of the client's own oi (Claude Code 2.1.296, bytes 211908840-211910460: e.indexOf("---",3)<0?null:e.match(RE)). With the check the pattern is tried only on a text that has a closing line, where it succeeds at the first candidate; 1 MiB of newlines now takes 2 ms. - tests: two extraction tests (five fillers of 64 KiB under a second; a closing line far below a long run still found) and one client_wiring test (the whole call under ten seconds, no bun asked). On the sources of 2723f8f the extraction tests fail at 19 s, 6 s and 9 s and the wiring test at 19 s (receipts/fail-before-precheck.txt); 4 of 4 mutants of the check are killed (receipts/mutants-precheck.txt). - The record has a section for this review, the evidence row and the receipts README rows. Nothing else changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…h before either launch, so a relative AGENT_FRONTMATTER_BUN or a relative PATH entry names the same file in the scratch directory The local codex re-review of b3da64d said "patch is incorrect" (one P2) and the command center adjudicated it as fixed in this landing, as one bounded commit. find_bun kept a relative AGENT_FRONTMATTER_BUN, or a bun found through a relative PATH entry (./bin), relative, and both launches of agent_names run with cwd=scratch, where subprocess looks for a relative executable relative to cwd (python/cpython@cbc944f4bc59, Doc/library/subprocess.rst L572-L576, Lib/subprocess.py L1912-L1919). The bun that parses from its absolute path gave Unknown from a relative path to the same file (reproduced with bun 1.4.3), and the check then said null and complete false. - scripts/adoption_status.py find_bun: the selection is made absolute before either launch. A value of the variable with a directory is made absolute against the caller's directory; a value with none is a name, looked up on PATH as a shell does, never as a file of the caller's directory; with no variable the first bun on PATH is found and made absolute; a caller whose own directory is gone cannot make a relative path absolute, which is None and so Unknown. Which bun is selected does not change (the mise shim remains the adjudicated follow-up). - tests: three stand-in tests (six framings of a relative selection run from the caller's directory; a name is a PATH lookup and never a file of the caller's directory; a vanished directory is Unknown) and one with the real bun, run from the bun's own directory. All four fail on the sources of b3da64d (receipts/fail-before-absolute-bun.txt); 9 of 9 mutants of the change are killed (receipts/mutants-absolute-bun.txt). - The record has a section for this review, the evidence row and the receipts README rows. Nothing else changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…malizing it, so a ".." after a link keeps the meaning the file system gives it The second local codex re-review of 9720257 said "patch is incorrect" (one P2) and the command center adjudicated it as fixed in this landing, as one bounded commit. find_bun made a relative selection absolute with os.path.abspath, which removes each ".." lexically, before the file system resolves the link in front of it: with current a link to actual/subdir, current/../bun is the file actual/bun and became the bun beside current, so healthy wiring gave Unknown (reproduced with bun 1.4.3). - scripts/adoption_status.py find_bun: the caller's directory is put in front of a relative path and nothing is normalized, so the components stay as written and the kernel resolves them at the launch as it resolves the relative path from the caller's directory (python/cpython@cbc944f4bc59, Doc/library/subprocess.rst L572-L576). An absolute value is passed as it is. A link is not resolved either: os.path.realpath would replace the mise shim, a link to mise, by mise, and an executable can be known by its name. - tests: a stand-in test (a relative value, a relative PATH entry and an absolute value through the link; a decoy of another version beside the link) and one with the real bun fail on the sources of 9720257 and pass here (receipts/fail-before-dotdot.txt); a test that a link to the bun keeps its name holds at both heads; 5 of 5 mutants are killed (receipts/mutants-dotdot.txt). The stand-in bun found its behavior file by os.path.abspath(sys.argv[0]), the same mistake; it now uses the real path of the file that ran. - The record has a section for this review, the evidence row and the receipts README rows. Nothing else changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…n is accounted in the push gate's advisory inventory The second re-review commit (31d377e) put the caller's directory in front of a relative bun path with os.getcwd(), which the OpenHands push gate's derivation counts as one unclassified read of scripts/adoption_status.py (os.path.abspath made the same read inside the library and was not counted). tests.test_runtime_worker_openhands_push_gate derives its reads from the committed tree; it was run on the working tree before that commit, so the continuous integration found the read first (validate-shard-2: test_advisory_baseline_and_explicit_categories_stay_bounded_on_this_repository and test_baseline_refresh_regression_accepts_a_committed_outer_observation). - blueprints/runtime-workers/openhands/evidence: the delta unclassified-gate-reads-20261013-pr1062-dotdot-delta.json (352 reads; the one new read, classified as a user status observation with the scope read-only client-wiring status; no worker execution authority) and its index entry, derived with the native GitTree and derive_ci_protected at the committed head. - the record and the receipts README name the read and the delta. Nothing else changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
seathatflowsinourveins
force-pushed
the
claude/cc-native-practice-agent-drift-check-20261013
branch
from
October 13, 2026 17:57
80f5522 to
4e3780b
Compare
…'s count, 353, and the freshness workflow provisions the bun its suite needs Two rulings of the command center on the landing rebase onto 77cc7ef, in one commit. - docs/decisions/2026-10-13-claude-agent-drift-check.md: the dotdot delta was re-derived over main after #1070 (344 of the baseline snapshot, scripts/adoption_status.py 4, tools/credentials/set_credential.py 4, scripts/validate.py 1), so it accounts for 353 reads; the sentence that describes it said 352, the count on the previous base. A record must match the delta it describes. - .github/workflows/catalog-freshness.yml (the local review's P1 on the rebased head): the freshness job runs the whole suite (python3 -m unittest --durations 50) under GitHub Actions, where the tests that need the real bun fail rather than skip (tests/test_adoption_status.py unavailable()), and only validate.yml provisioned the pinned bun, so the scheduled and the manual run failed at RealBunTests and the vendor-definition tests and its propose job never started. The job now has the same step, with the same version and checksum, before its suite. - tests/test_adoption_status.py: test_every_job_that_runs_the_suite_provisions_the_pinned_bun_before_it reads every workflow, finds each job whose unittest step reaches the module (a bare unittest, a named tests.test_adoption_status, the shard runner) and holds the step, the version and the checksum equal to validate.yml's; it fails at the freshness job before the change (receipts/fail-before-freshness-bun.txt). test_the_reader_tells_the_suite_runners_from_the_named_modules holds the reader's own cases. - Evidence: receipts/freshness-bun-local-check.txt runs the step's own script and the module (215 tests OK with the exported variable, FAILED without it); 13 of 13 mutants killed (receipts/mutants-freshness-bun-13.txt); the record has a section for the review. The workflow was not dispatched: its propose job force-creates an evidence branch and opens a pull request. - Nothing else changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
seathatflowsinourveins
deleted the
claude/cc-native-practice-agent-drift-check-20261013
branch
October 13, 2026 20:17
This was referenced Oct 13, 2026
seathatflowsinourveins
added a commit
that referenced
this pull request
Oct 14, 2026
…opy, refuses a folder there and follows CLAUDE_CONFIG_DIR for the current user's own install (CPA-V-10 follow-up) The drift check of #1062 counts a link at a shipped agent name and reads the Claude home from CLAUDE_CONFIG_DIR; the installer did neither, so it could not clear what the check reports. Measured on Claude Code 2.1.296 (zero model spend; the client killed at its init record, receipts/client-agents-probe.json): with CLAUDE_CONFIG_DIR set the client lists the definitions under that folder's agents/ and none under <home>/.claude/agents; an empty value is read as its working directory and a relative one is resolved against it; a link to a file is followed (listed by its front matter name) and a link to nothing is not loaded. - tools/adoption/install_claude_profile.py: install_agents writes each copy beside its destination and renames it over (os.replace), so a link at a shipped name (to a file, to nothing, to a folder) is replaced by a regular copy and never written through (shutil.copy2 opened the destination through the link, python/cpython@cbc944f4bc59 Lib/shutil.py L262); the equality test no longer follows a link; a folder at a shipped name is refused before anything is copied (copy2 copied into it, a nested definition the client then loads); a dry run names the link it would replace. - Without --home the agents step follows a set, absolute CLAUDE_CONFIG_DIR (made absolute and NFC-normalized as the client reads it); an empty value is read as unset, as the drift check reads it; a relative value is refused; with --home the target is that home's own .claude whatever the environment names (the new-WSL tool passes --home and strips the variable). A relative value does not stop a run that does not install agents. - tests/test_install_claude_profile.py: AgentsInstallTests and AgentsConfigDirTests (15 tests; 9 fail or raise on the sources of 329985d, receipts/fail-before.txt); 15 of 15 mutants are killed (receipts/mutants-installer.txt; the first run killed 14 and the survivor's test now asserts that a dot-dot is collapsed without the file system). - Docs and records: docs/decisions/2026-10-13-installer-agent-links-and-config-dir.md; adoption/bootstrap.md (the agents bullet); a pointer from the "Not covered" bullet of the drift record; evidence/artifacts/ installer-agent-links-20261013/ (value-free captures: definition names and counts). Not changed: the guard hooks (registered by absolute command), the routing skill and the workflows steps, which are found by folder and need their own measurement of where the client looks (a follow-up). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
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.
Scope
claude-practice-audit-converged-20261012, group CPA-V-10):scripts/adoption_status.py --client-wiringreportsclaude.stack_agents_drift, the number of Claude agent definitions under the user home'sagents/, at any depth, that differ from this checkout'sadoption/agents/claude(byte for byte; four vendor-installed search definitions are listed, not counted unless their front matter declares the name of a shipped definition, which Bun.YAML.parse reads, run through a bun of the version the client embeds, and is unknown where that cannot be done), and the wiring rule requires 0. The folders are read through descriptors, never through a link. Before it, a fixture with one stale user agent reportedcomplete: true.77cc7ef42a55ca6b7c18ab24653000b687eb2a0d(main after Fingerprint the reviewed narrative commit ids the merge-commit history scan now reaches #1072, fix(ci): enforce PR-owned manifest registration continuity #1070 and Let the CC's adjudication decide a P2-only "patch is incorrect" local review; port SQUASH_BODY_FILE #1075; rebased for landing in three registry-and-index-only steps fromc63c83a82, main after Rule text: converged candidates by multi-angle convergence, models only through a converged harness #1049, through3f1a921dd, main after Credential inventory: github-review-app key file and a --from-file custody mode #1057 and Route each Claude agent definition to its table's model and effort #1045, and993cc890b3ff3fc95fa7cdba51edd726565f9ace, main after Keep lane-A journal continuity for session-only config changes #1068)lane:foundationscripts/adoption_status.py,tests/test_adoption_status.py,tests/test_agent_drift_record.py(new, round 2),docs/token-efficiency-stack.md,docs/decisions/2026-10-13-claude-agent-drift-check.md(new),evidence/artifacts/agent-drift-check-20261013/(new),catalogs/foundation/upstream-surface-dispositions.json(one citation, first review fix),blueprints/runtime-workers/openhands/evidence/(the advisory index and one dated delta, round 2),tools/adoption/host-baseline.md(one locator, round 2),.github/workflows/validate.yml(the bun step, design round),tests/fixtures/agent_frontmatter/client-oracle.json(new, design round),manifests/evidence.json.77cc7ef42(the tenth,eecd506f2, is the commit of the review of the rebase below) (the heads named in the reads and rounds below are those on993cc890b, the base of the second rebase, unless a text says otherwise): the change (d629d10ba), the first review fix (137e2b198), round 2 (8d8c66180), the design round (4222bdd82), the record update (e3bdca462), the delimiter precheck after the local codex review (b3da64d76, below), the absolute bun path after the local codex re-review (9720257e7, below, a fast-forward ofb3da64d76), the path made absolute without normalizing it after the second re-review (31d377e3c, below, a fast-forward of9720257e7) and the advisory delta for the read that commit adds (80f552245, below, found red by the continuous integration). On3f1a921dd(the first rebase) they weree72d8415a,69ee3efab,ebc3520ce,007ded7db,2723f8fd0andf8008637; before ite70d2de0a,e22358e46,e7260fd3c,fb968aff6and3733443f0; the reads name those heads. Before the rebase they weree70d2de0a,e22358e46,e7260fd3c,fb968aff6and3733443f0; the reads below name those heads. On77cc7ef42(the rebase below) the first nine are11a1f318b,5b180be51,3a4068c41,9a3a6ea85,30e3072e3,98f9459cd,08e044771,e8047fb9eand4e3780b10, and the tenth iseecd506f2.Why
The client serves the definitions in
~/.claude/agents/to every project that does not define the name itself, andtools/adoption/install_claude_profile.pycopies the 11 ofadoption/agents/claudethere verbatim. Nothing compared them afterwards: the audit found five of 11 host copies stale at 06:27Z on 2026-10-12 (fixed since: 11 of 11 equal, checked again here),scripts/adoption_status.pyhad no reference to the Claude agent paths, and the same drift class produced the incident settled in CPA-V-37. A stale copy is old text; a wider one is a different risk, since an omittedtoolsfield means every tool.The change
claude.stack_agents_drift*.mdofadoption/agents/claudeis installed byte for byte at the top of<Claude home>/agents(CLAUDE_CONFIG_DIRwhen set) and nothing else of that kind is in the folder at any depth but the four listed vendor definitions. Counted: a shipped copy that is absent, a link, a folder or another kind of file, or not equal (a stale copy and a wider one are both bytes that differ); every other*.mdat any depth, hidden files and a link to a file named*.mdincluded, whether or not it declares a name (apart from the four vendor names, the check reads no front matter of a file it does not ship, so a nameless one, which the client skips, counts: remove or rename it), because the client loads the folder recursively. Not counted: a pipe, a socket, a device and a link to nothing (the client's walker keeps regular files only), backups, other extensionsVENDOR_HOST_AGENTScodebase-memory,codebase-memory-auditor,codebase-memory-scout,semble-search: installed by the tools they route to, named as the search roles intools/local-pages/architecture_mapping.json; a listed exception at the top of the folder, as the audit item says, unless the file's front matter declares thenameof a shipped definition (the client keys a definition by itsname; of two files that declare one, one is chosen by read order). Below the top, or as a link, a vendor name is not exemptagent_front_matter,agent_names/^---\s*\n([\s\S]*?)---\s*\n?/captures), then every block of the check (the 11 shipped definitions and the vendor-named files, at most 15) goes in one call toBun.YAML.parsethroughbunatCLIENT_BUN_VERSION(1.4.3, the version the installed client embeds), in an empty scratch directory with an empty configuration, that directory as home and an environment ofPATH,HOMEandLANGonly; the blocks are JSON on stdin. An answer is a name, none or a throw; a throw is unknown (the client's second attempt is not reproduced). No bun, another version, a bun that fails or does not answer in 30 s: unknown, never 0. A vendor file with no front matter needs no bun. The hand-written reader of rounds 1 and 2 is removednullnullleaf it makescompletefalseagentswithO_DIRECTORY | O_NOFOLLOW, then every entry withO_NOFOLLOWrelative to the descriptor of its folder, and what is described isfstatof the descriptor that was opened: nothing is inspected by path and then opened by path. The shared readerread_regular_bytesand its Codex callerstack_roles_matchingread the same waystack_agents_driftmust be 0 (unlikestack_roles_matching, which is information): the audit's fail-first is a stale agent that must fail the status.docs/token-efficiency-stack.mdcarries the ruleSOTA sources
plugins/plugin-dev/skills/agent-development/SKILL.mdL152-L154: thetoolsfield of an agent definition, "If omitted, agent has access to all tools" (why a wider copy is a risk and not only old text);CHANGELOG.mdL4147 (release 2.1.198: subagents are managed by editing.claude/agents/directly), L7088 (release 2.1.43: "Fixed spurious warnings for non-agent markdown files in.claude/agents/directory", which shows that the client reads markdown files in that folder and skips those that are not agents; it does not say how it walks the folder; the first description of this change read it as a warning about non-agent files) and L8578 (release 1.0.6: "Respect CLAUDE_CONFIG_DIR everywhere"). The command center's read cited L8578 for the merge order: the line is theCLAUDE_CONFIG_DIRentry, and the order is on the documentation page and in the loader (next bullet).--agentsflag, the project, the user~/.claude/agents/, plugins) and L176-L182 ("scans.claude/agents/and~/.claude/agents/recursively"; identity comes only from thenamefront matter field; of two files under one tree that declare a name, "Claude Code loads only one of them, chosen by filesystem read order rather than a documented precedence"). The installed client 2.1.296 was read for the loader: the walker (OVn) enters every folder, follows symbolic links, skips a folder it already visited and keeps files whose names end in.md; the alternative path (gU) isrg --files --hidden --follow --no-ignore --glob *.md, so hidden files are listed; a file without a front matternameis skipped without a message (pHo); the active set (d2) is built built-in, plugin, user, project, flag, managed, a later definition of a name replacing an earlier one.Modules/_io/fileio.cL404 (the ordinaryopenpasses the path toopen(2)as given, so a path checked first and opened after is read through a replacement),Doc/library/os.rstL1272 (os.open,dir_fd), L1364-L1365 (O_DIRECTORY,O_NOFOLLOW), L3507 and L3551 (os.supports_dir_fd,os.supports_fd),Lib/shutil.pyL262 (copyfileopens the destination for writing, through a link: the installer follow-up).WS,BL,oi/W9t/C/I,Vk,Iwt,pHo,d2;receipts/client-oracle.txt) and its embedded engine,Bun v1.4.3 (eecfd55de), which is not a public commit; the client itself, started on each text of a corpus as a project agent, is the oracle (the loader, the parser and the engine are its own).bun-v1.4.3(published 2026-10-10, commitc6da4a4d3):bun-linux-x64.zip, sha2563e730528d89775f03f87f05c06787c4df4099d0a37f3f252e7b829cb23816e18as the release'sSHASUMS256.txtlists it, the pin of.github/workflows/validate.yml;Bun.YAML.parseis the engine call the client'sBLmakes. Bun 1.4.2 (the first host's mise): answered alike on the corpus.blueprints/runtime-workers/openhands/evidence/advisory-gate-deltas.mdandresolver/push_gate.pyderive_ci_protectedatc63c83a82(append a dated delta; the nativeGitTreederivation; earlier snapshots unchanged).tools/adoption/install_claude_profile.pyinstall_agents(the verbatim copy to~/.claude/agents),scripts/adoption_status.pystack_roles_matching(the Codex precedent: link, folder and absent cases,nullfor what cannot be read),tools/local-pages/architecture_mapping.json(the four search roles),docs/decisions/2026-10-13-claude-agent-drift-check.md.Evidence-class table
complete: true; nowstack_agents_driftis 1,completeis false and every other flag is as it wasClientWiringTests.test_a_stale_or_wider_user_agent_makes_the_wiring_incomplete; 16 of the 153 tests of the module fail on the script atc63c83a82(two constants added so that they fail on behavior),receipts/fail-before.txtnull; the home followsCLAUDE_CONFIG_DIRClientWiringTestsof the first commit; 20 of 20 mutants killed (receipts/mutants-20.txt)*.mdare counted; a pipe, a socket, a device and a link to nothing are not; a link to a folder, a vanished entry, a tree past its bound and an oversize copy arenullreceipts/mutants-review.txt(49 of 49 killed, the first review fix) andreceipts/mutants-round2.txt(92 of 92 killed, the whole list on the final tests)receipts/client-oracle.txt(16,428 texts: 0 misses; the hand-written reader ofe7260fd3: 501);receipts/fail-before-design.txt.github/workflows/validate.yml;test_the_validation_workflow_installs_the_bun_the_check_requiresreceipts/mutants-design.txt(38 of 38 killed)unclassified-gate-reads-20261013-pr1062-delta.json;tests.test_runtime_worker_openhands_push_gate(127 tests: two failures ate22358e46, none now)tests/test_agent_drift_record.py(fails on the record ofe22358e46: it quoted 46 of 46 for a receipt of 49)null, never 0; the Codex role count the sametest_a_replacement_of_...andtest_the_codex_role_count_is_never_read_through_a_replacement_either(each tries the replacement at every point between two calls);receipts/fail-before-review.txtcompletetrue (before and after each review round)python3 scripts/adoption_status.py --profile token-efficiency --client-wiring --json(receipts/host-run.json);cmpof the 11 filesLocal commands run
Decision record
docs/decisions/2026-10-13-claude-agent-drift-check.md: the context, the decision, the evidence, what is not covered and the conditions that overturn it.Host evidence
Only relevant when this PR adds or changes files under
evidence/hosts/. This PR changes none.Checklist
run:step tovalidate.ymlthat downloads the bun release archive and checks its sha256 before unpacking it.permissions: {}and grant each job only what it needs. None added;validate.ymlis edited in one job (therun:step above), and its top-levelpermissions: {}and the job's grants are unchanged.Review of
e70d2de0a: Astra's first read and the command center's Claude read, one fix commitcomplete: true; the reproducer schedules the replacement between the reallstatand the realopen)test_a_replacement_of_a_shipped_file_at_any_point_is_never_read_through,test_a_replacement_of_the_agents_folder_at_any_point_is_never_read_through,test_a_replacement_of_a_folder_below_the_top_at_any_point_is_never_read_through,test_the_codex_role_count_is_never_read_through_a_replacement_either(file and folder),test_the_folders_are_opened_below_a_descriptor_without_following_a_link,test_a_platform_that_cannot_refuse_links_reads_nothing,test_read_regular_bytes_answers_for_every_kind_of_entry_without_following_a_linkagents/old/stack-verifier.md: 0 andcomplete: true)*.mdat any depth is counted; the test that expected a nested file to be ignored is reversedtest_a_definition_below_the_top_is_drift_at_any_depth,test_the_walk_is_bounded_and_a_tree_past_the_bound_is_unknown_not_a_count,test_links_and_other_kinds_below_the_top_are_counted_or_unknown_never_followedcodex:host:RTK.md, and two citation assertions failedcatalogs/foundation/upstream-surface-dispositions.jsoncitesscripts/adoption_status.py:532-553(codex_instructionsandrtk_instructions_inlinewhole; a range that holds the instruction names whether lines are counted bysplitlines()or by LF, which differ by two because of Unicode separators inRUST_WHITESPACE); the registry binding is refreshedtests.test_upstream_surface_watch(116 tests, two failures ate70d2de0a)test_a_vendor_file_that_declares_the_name_of_a_shipped_definition_is_drift,test_a_vendor_name_below_the_top_or_as_a_link_is_not_the_vendors_own,test_a_vendor_file_that_cannot_be_read_is_not_drift_for_what_it_might_declare--profile token-efficiency; (Astra P3) the changelog paraphrase overstated its linescripts/validate.py; the registry checkTwo corrections to what the first review relayed, both from the primary sources: the changelog line cited for the merge order (L8578) is the
CLAUDE_CONFIG_DIRentry, and the priority is documented on the sub-agents page andimplemented in the loader (above); and the installer is not a clean recovery path (next section).
The behavior changes to know: a replacement made after the check looked at an entry is read as the object that was opened (or refused, if it was a link when opened); a link to a folder anywhere below the agents folder makes the count
null; a nested definition now makescompletefalse.Review of
e22358e46: Astra's second read and the command center's Claude read, one round-2 commitSuperseded in part: the name reader (
agent_identity) and the tests that name it were replaced in the design round (the next section); the rows stay as the record of round 2.scripts/adoption_status.py);tests.test_runtime_worker_openhands_push_gatefailed two tests ate22358e46GitTreeandderive_ci_protected: 347 reads against the 344 of the baseline it names; the three added reads are the opens inopen_below,stack_roles_matchingandstack_agents_drift; the 24 location changes are line moves (9 in this script, 15 inscripts/skills_status.pyandtools/adoption/install_skills.py, which other pull requests moved since the baseline); the index entry is appended, every earlier snapshot and delta is unchanged, both files are registered.source_commitis null: the input is a local commit of this round's content before the delta existed, and the published head holds the same scriptstests.test_runtime_worker_openhands_push_gate(127 tests;test_advisory_baseline_and_explicit_categories_stay_bounded_on_this_repositoryandtest_baseline_refresh_regression_accepts_a_committed_outer_observationfail ate22358e46)name:line only: a vendor-named file declaring a shipped name through a quoted key, a block scalar, an escape or a byte order mark gave 0 andcomplete: trueagent_identityreplaces the regex: the client's block extraction and a strict reader of the plain spellings, unknown for every other; the shipped definitions' names are read the same way; the first review's claim is narrowed in the record to what is measuredtest_a_vendor_file_that_declares_the_name_of_a_shipped_definition_is_drift(every row asserts the count andcomplete),test_a_vendor_file_whose_name_is_spelt_in_a_way_the_reader_does_not_take_is_unknown_and_incomplete(27 spellings, among them the reviewer's quoted key and escape),test_the_name_reader_gives_what_the_clients_parser_reads_or_says_unknown,test_every_shipped_definition_declares_a_name_the_reader_takes,test_a_shipped_definition_whose_name_cannot_be_read_makes_the_count_unknown,test_a_vendor_file_over_the_clients_size_limit_is_unknownmutants-review.txt, whose receipt and README hold 49tests/test_agent_drift_record.py*.md, or a*.mdwith noname, counted although the walker skips it*.mdwith nonamestays counted and the record and the docstring say whytest_a_link_to_a_file_is_counted_and_what_the_clients_walker_skips_is_notELOOP, measured on the first host's Linux 6.18;EMLINKis accepted too)test_a_link_is_told_by_the_answer_of_the_open_and_not_by_a_later_lookname:with the value on the next lineagent_identitytest_the_name_reader_gives_what_the_clients_parser_reads_or_says_unknowntools/adoption/host-baseline.mdcitesadoption_status.py:1124-1134for the default launcher root, which movedlauncher_resolutionand keeps the old range as the one at its reference commitReview of
e7260fd3: the command center's Claude delta, Astra's second delta and the adjudication, one design-round commitname:and its indented value made the hand-written reader return none, so drift 0 andcomplete: true;Bun.YAML.parseand the client read the nameBun.YAML.parsethroughbunat the version the client embeds, in one call, failing closed (no bun, another version, a throw, a bun that does not answer: unknown)RealBunTests.test_the_counterexample_of_the_command_centers_read_and_its_variants_are_read_as_the_client_reads_them,ClientWiringTests.test_a_vendor_file_that_declares_the_name_of_a_shipped_definition_is_drift(68 rows, each the client's),RealBunTests.test_the_pipeline_never_reads_a_name_other_than_the_one_the_client_served(2,987 texts),AgentNamesTests,NeedsBunTeststests/test_agent_drift_record.pytest_every_test_the_record_and_the_readme_cite_existsIwt's other refusalsreceipts/client-oracle.txtnames the whole path by function and byte interval; the record statesIwt's refusals;identity-oracle.txtis marked supersededadoption_status.py:1426as move-only although this PR edited that lineadoption_status.py:1377stayssource_line_move_onlywith a scope that says the line itself was edited (it hands the agent definitions' source folder to the client wiring) and the read is unchanged; its derivation text names ittests.test_runtime_worker_openhands_push_gate(127 tests)The behavior changes to know: on a host whose
bunis not 1.4.3 (the first host's mise has 1.4.2)stack_agents_driftisnulland the wiring incomplete until bun 1.4.3 is installed by its supported path (mise install [email protected]) or named inAGENT_FRONTMATTER_BUN: a step for the host owner, not applied here; a vendor-named file whose front matter the first parse throws on is unknown; andvalidate.ymldownloads and checksums bun 1.4.3 in every shard.Landing rebase onto
3f1a921dd(2026-10-13), after Astra's ACK and the command center's PASS_WITH_P3 atfb968aff6The branch was rebased once, onto main after #1057 (which landed with a delta of its own) and #1045, with the owning tools and no hand edit of a derived file; the pull request had gone
DIRTYonmanifests/evidence.jsonand the advisory delta index. The record update was the last commit (the command center's two conditions: the sentence at R:57 that a successful parse with no string name is none and departs from the adjudication's wording on purpose, with the oracle's numbers, and thatmise install [email protected]does not change the active version).net_diff_compare.py, the patch against the old merge basec63c83a82against the patch against the new one): 25 files before and after, none only on one side; 22 have the same patch; the 3 that differ are the ones that had to:manifests/evidence.json(98 added and 13 removed lines on both sides; the rows were re-registered withhost_receipts.register_filefor the files each commit changes, on main's file) and the two advisory deltas (re-derived, below).registry_rows_check.py): 24 PR-owned rows at the old head, 0 missing at the new one; 4 have another hash on purpose: the delta index, the two deltas, andcatalogs/foundation/upstream-surface-dispositions.json, which Credential inventory: github-review-app key file and a --from-file custody mode #1057 also edited (the PR's patch to it is among the 22 unchanged; the row follows the merged bytes).GitTreeandderive_ci_protectedover the tree that now holds Credential inventory: github-review-app key file and a --from-file custody mode #1057's reads: 351 reads against the 344 of the baseline snapshot (the 3 descriptor-bound opens of this change and the 4 oftools/credentials/set_credential.py, whose scope is the one Credential inventory: github-review-app key file and a --from-file custody mode #1057's delta gave them); 33 line moves;source_line_move_onlyfor the edited line:1377keeps the scope that says the line was edited.2723f8fd0(idle-class, read at that head; a rerun at that commit returns the same):python3 scripts/validate.pyexit 0 (11,899 files);tests.test_runtime_worker_openhands_push_gate127 OK;tests.test_adoption_status203 OK with bun 1.4.3 (AGENT_FRONTMATTER_BUN),tests.test_agent_drift_record,tests.test_evidence_manifest,tests.test_upstream_surface_watch,tests.test_adoption_docs_consistency,tests.test_host_baseline_probe,tests.test_validate,tests.test_workflow_hardening,tests.test_workflow_security_coverage,tests.test_credential_toolsandtests.test_credential_statusOK;git merge-tree --write-tree origin/main HEADclean; the privacy scan of the added lines: 0 hits.Review of
2723f8fd: the local codex review, one P2, one scope-locked commitThe branch was rebased a second time, onto main after #1068 (a registry-only conflict again; 27 of 27 files have the same patch against the previous head, 26 PR-owned registry rows, 0 missing, 0 changed, the two advisory deltas keep their bytes; the table of patch-ids is
/var/tmp/ccnp-tests-tmp/perfile-patchids-1062b.txt), and the checks below are on that head.The local codex review of the rebased head said "patch is incorrect" (one P2) and the lander blocks on a completed review; the command center ruled one bounded round, scope-locked to it. Astra's rebase micro at
2723f8fdis an ACK with no blockers.scripts/adoption_status.py:1084: the extraction matched the front matter pattern without the client's delimiter precheck, so a vendor-named file of---and then whitespace with no closing line made the pattern backtrack quadratically (reproduced: 64 KiB of newlines took 18.9 s, 32 Ki pairs of a space and a newline 10.7 s; a file may be 1 MiB); the extraction runs before bun, so no timeout of bun bounds itagent_front_matterreturns none when"---"does not occur after the first three characters: the check of the client's ownoi(Claude Code 2.1.296, bytes 211908840-211910460:e.indexOf("---",3)<0?null:e.match(RE)). It changes no result and removes the backtracking: the pattern is tried only on a text that has a closing line. 1 MiB of newlines now takes 2 msExtractionTests.test_an_unterminated_block_of_whitespace_is_refused_in_linear_time(five fillers of 64 KiB under a second),ExtractionTests.test_a_closing_line_far_below_a_long_run_of_whitespace_is_still_found_in_linear_time(a control),ClientWiringTests.test_a_vendor_file_that_is_one_long_unterminated_block_is_read_in_linear_time(the whole call under ten seconds, no bun asked); the first and the third fail on the sources of2723f8fd(19 s, 6 s, 9 s and 19 s:receipts/fail-before-precheck.txt); 4 of 4 mutants of the check are killed (receipts/mutants-precheck.txt)Checks at
b3da64d76(idle-class):python3 scripts/validate.pyexit 0 (11,903 files, read at that head: this line first said 11,901, a count read before the last registry rows; Astra's read of that head returned 11,903 twice and a rerun here at that commit returns 11,903);tests.test_adoption_status206 OK with bun 1.4.3 underGITHUB_ACTIONS=true;tests.test_agent_drift_record,test_evidence_manifest,test_upstream_surface_watch,test_adoption_docs_consistency,test_validate,test_workflow_hardening,test_workflow_security_coverage,test_host_baseline_probe,test_credential_tools,test_credential_statusOK andtests.test_runtime_worker_openhands_push_gate127 OK; the push-gate inventory is unchanged (no read added: the module passes without a new delta); privacy scan of the added lines: 0 hits.Review of
b3da64d76: the local codex re-review (one P2) and Astra's read (the description only), one scope-locked commitThe local codex re-review of
b3da64d76said "patch is incorrect" (one P2) and the lander blocks on a completed review; the command center adjudicated it as fixed in this landing, as one bounded commit. Astra's read of the same head confirms the precheck independently (the 2.1.296 bytes match; 65,536 newlines 17.9 s before and 0.000066 s after, 32,768 space and newline pairs 9.08 s and 0.000046 s; 0 extraction differences on 2,987 oracle texts and on 2,993 upstream-oiinputs; the Bun replay has 0 misses; the search-start 3 to 4 mutant survives and is behaviorally equivalent) and is CHANGES_REQUESTED on one P2 in this description only.scripts/adoption_status.py:1116:find_bunkept a relativeAGENT_FRONTMATTER_BUN, or a bun found through a relativePATHentry (./bin), relative, and both launches ofagent_namesrun withcwd=scratch; subprocess looks for a relative executable relative tocwd(python/cpython@cbc944f4bc59639a444dd971c737788ba2283a91Doc/library/subprocess.rstL572-L576;Lib/subprocess.pyL1912-L1919), so the name was looked up in the scratch directory. Reproduced with bun 1.4.3 (the receipt): the absolute path parses and a relative path to the same file gives Unknown, and the check then reportsnullandcompletefalsefind_bunreturns an absolute path before either launch: a value with a directory is made absolute against the caller's directory; a value with none is a name, looked up onPATHas a shell does and never as a file of the caller's directory; with no variable the firstbunonPATHis found and made absolute; a caller whose own directory is gone cannot make a relative path absolute, which is Unknown. Which bun is selected does not changetest_a_relative_bun_is_made_absolute_before_either_launch(six framings),test_a_value_with_no_directory_is_a_name_looked_up_on_path_and_never_in_the_callers_directory,test_a_relative_bun_of_a_caller_whose_directory_is_gone_is_unknownand, with the real bun,test_a_bun_named_by_a_path_relative_to_the_callers_directory_is_the_same_bun(run from the bun's own directory); all four fail on the sources ofb3da64d76(receipts/fail-before-absolute-bun.txt: the reviewer's scenario gives Unknown there and the name here); 9 of 9 mutants of the change are killed (receipts/mutants-absolute-bun.txt)pr-body.md:163: it gavepython3 scripts/validate.pyexit 0 (11,901 files) forb3da64d76, which returns 11,9032723f8fd0, below); the count at this head is belowChecks at
9720257e7(idle-class, read at this head):python3 scripts/validate.pyexit 0 (11,905 files);tests.test_adoption_statusandtests.test_agent_drift_record213 OK with bun 1.4.3 underGITHUB_ACTIONS=true; the modules that name a changed file, one process each: 70 modules, 4,659 tests, 69 exit 0; the one other is tests.test_token_e2e_grader (test_keys_at_the_pinned_revision: a test clone hard-links across devices, the same failure as at every earlier head); tests.test_adoption_status 210 OK, tests.test_agent_drift_record 3 OK, tests.test_runtime_worker_openhands_push_gate 127 OK (the inventory is unchanged: no read added and no new delta), tests.test_host_baseline_probe 28 OK;git merge-tree --write-tree origin/main HEADclean (exit status read directly); the privacy scan of the lines added sinceb3da64d76: 0 account, host, home-path, e-mail, non-ASCII or token hits (gitleaks on the commit: no leaks).The mise shim stays outside this fix, as adjudicated: with no variable the selection is still the first
bunonPATH(now as an absolute path), so a shim that resolves to another version is Unknown by the version check; the variable naming the installed 1.4.3 binary stays the way (the follow-up below).Review of
9720257e7: the second local codex re-review (one P2), one scope-locked commitThe second local codex re-review said "patch is incorrect" (one P2) and the command center adjudicated it as fixed in this landing, as one bounded commit, pushed at once.
scripts/adoption_status.py:1105:os.path.abspathmade the selected bun absolute by removing each..lexically, before the file system resolves the link in front of it; withcurrenta link toactual/subdir,current/../bunis the fileactual/bunand became thebunbesidecurrent, so healthy wiring gave Unknown (reproduced with bun 1.4.3)find_bunputs the caller's directory in front of a relative path and normalizes nothing: the components stay as written and the kernel resolves them at the launch as it resolves the relative path from the caller's directory (python/cpython@cbc944f4bc59639a444dd971c737788ba2283a91Doc/library/subprocess.rstL572-L576); an absolute value is passed as it is; a link is not resolved (os.path.realpathwould replace the mise shim, a link tomise, bymise: an executable can be known by its name)test_a_dot_dot_after_a_link_keeps_the_meaning_the_file_system_gives_it(a relative value, a relativePATHentry, an absolute value through the link; a decoy of another version beside the link) and, with the real bun,test_a_dot_dot_after_a_link_reaches_the_real_bun_the_kernel_finds: both fail on the sources of9720257e7(receipts/fail-before-dotdot.txt: Unknown there, the name here);test_a_link_to_the_bun_is_kept_by_its_name_since_an_executable_can_be_known_by_itholds at both heads (the guard against resolving the final component); 5 of 5 mutants killed (receipts/mutants-dotdot.txt)The stand-in bun of the tests found its behavior file by
os.path.abspath(sys.argv[0]), the same lexical mistake; it now uses the real path of the file that ran.Checks at
31d377e3c(idle-class, read at this head):python3 scripts/validate.pyexit 0 (11,907 files);tests.test_adoption_status,tests.test_agent_drift_record,tests.test_evidence_manifestandtests.test_validate: 307 tests OK with bun 1.4.3 underGITHUB_ACTIONS=true;tests.test_runtime_worker_openhands_push_gate127 OK at80f552245(the module derives its reads from the committed tree: run on the working tree before the commit it passed, and the continuous integration of31d377e3cfailed it,validate-shard-2; see the next paragraph),test_upstream_surface_watch116,test_adoption_docs_consistency39,test_host_baseline_probe28,test_workflow_hardening118,test_credential_tools52,test_credential_status52 andtest_workflow_security_coverage10 OK (the 70-module sweep of the previous head is above; this head's sweep is the eight modules that the change can reach);git merge-tree --write-tree origin/main HEADclean against mainba6afd8d7(exit status read directly); the privacy scan of the lines added since9720257e7: 0 account, host, home-path, e-mail, non-ASCII or token hits (gitleaks on the commit: no leaks).The advisory delta of
31d377e3c(80f552245): a read that the second re-review commit addsos.getcwd()infind_bun(scripts/adoption_status.py:1107) is a read of the working directory that the OpenHands push gate's derivation counts as one unclassified read (os.path.abspathmade the same read inside the library and was not counted). My earlier statement here that this round added no read was wrong: I rantests.test_runtime_worker_openhands_push_gateon the working tree before committing, and the module derives its reads from the committed tree, so the continuous integration (validate-shard-2:test_advisory_baseline_and_explicit_categories_stay_bounded_on_this_repositoryandtest_baseline_refresh_regression_accepts_a_committed_outer_observation) found it. The read is real and read-only, so it is accounted, not written around:blueprints/runtime-workers/openhands/evidence/unclassified-gate-reads-20261013-pr1062-dotdot-delta.json(352 reads; the new read classified as a user status observation, scope "read-only client-wiring status; no worker execution authority") and its index entry, derived with the nativeGitTreeandderive_ci_protected. At80f552245:python3 scripts/validate.pyexit 0 (11,908 files),tests.test_runtime_worker_openhands_push_gate127 OK on the committed tree, gitleaks no leaks. Another pull request that appends a delta to the same index (V-07 #1074 does) re-derives its cumulative delta after this one lands, as #1057's landing required of this one.Landing rebase onto
77cc7ef42(2026-10-13), after Astra's ACK at80f552245and the command center's approval of one leased registry-only rebaseThe pull request went
DIRTYonmanifests/evidence.jsonand the advisory delta index as main moved (#1072, #1070, which landed a read delta of its own forscripts/validate.py, and #1075). It was rebased once, commit by commit, with the owning tools and no hand edit of a derived file: each commit is cherry-picked onto main; a conflict in the registry or the index takes main's file and the pull request's part is re-derived (host_receipts.register_filefor the rows the commit changed; the delta of each of the three commits that carried one is re-derived with the nativeGitTreeandderive_ci_protectedover the tree that now holds #1070's read). The command center's stop rule (a change beyond the registry and index files is not a registry-only rebase) held: no other file differs.git patch-id --verbatimof the-U0 --binarydiff against each side's own merge base: old head80f552245against993cc890b, new head4e3780b10against77cc7ef42): 32 files before and after, none only on one side; 28 have the same patch; the 4 that differ are the ones that had to:manifests/evidence.jsonand the three advisory deltas (re-derived, below). The delta index has the same patch: the same three entries, appended after main's.8d8c66180to3a4068c41,4222bdd82to9a3a6ea85,80f552245to4e3780b10)..github/workflows/validate.yml, which main also edited (the pull request's hunk is among the 28 unchanged patches; the row follows the merged bytes); the other 11,879 rows are main's, unchanged, and no row is added beyond main's and the pull request's.python3 scripts/validate.py --registration-base origin/main --registration-before "<80f552245> <fb968aff6> <2723f8fd0>"(fix(ci): enforce PR-owned manifest registration continuity #1070's gate, with the old head and the two force-pushed predecessors of the pull request's timeline): exit 0.scripts/adoption_status.py3, then 4 with theos.getcwd()read offind_bun;tools/credentials/set_credential.py4, with the scope Credential inventory: github-review-app key file and a --from-file custody mode #1057's delta gave them;scripts/validate.py1, with the scope fix(ci): enforce PR-owned manifest registration continuity #1070's delta gave it); 33 line moves;source_line_move_onlyfor the edited line keeps the scope that says the line was edited.source_commitis null as in the earlier entries (each delta is derived from the local commit that precedes it; the published head holds the same scripts).scripts/adoption_status.py4 of the delta's added") stated the delta as it was derived on993cc890b; the rebased delta is 353 (main'sscripts/validate.pyread). The rebase changed no text, so the command center ruled the correction into the next commit (below).4e3780b10(idle-class, read at that head):python3 scripts/validate.pywith the registration gate above exit 0 (11,910 files hashed);tests.test_runtime_worker_openhands_push_gate127 OK on the committed tree;tests.test_adoption_statusandtests.test_agent_drift_record216 OK with bun 1.4.3 (AGENT_FRONTMATTER_BUN; with the host's PATH shim, bun 1.4.2, the two tests that need 1.4.3 fail by design, "so its absence there is a defect"),test_evidence_manifest5,test_validate86,test_registry_rows25 (new on main),test_workflow_hardening118,test_workflow_security_coverage10,test_adoption_docs_consistency39,test_upstream_surface_watch116,test_host_baseline_probe28 andtest_gitleaks_config58 OK;git merge-tree --write-tree origin/main HEADexit status 0 (read directly, right before the push); identities all the no-reply one; gitleaks on the nine commits: no leaks; the privacy scan of the added lines: 0 account, host, home-path, e-mail, non-ASCII or token hits (the same 36 digests as before).Review of
4e3780b10: the local review of the landing rebase (one P1), folded with the count into one commitThe local review of the rebased head (normalized
71a2fd4f, sha256 prefix) found one P1; the command center ruled it into the one commit that also corrects the count above. The commit is a fast-forward of4e3780b10(merges clean against main24a209016, which landed #1080 meanwhile:git merge-tree --write-tree, exit status read directly).tests/test_adoption_status.py:1932-1933: the bun tests fail under GitHub Actions rather than skip, andcatalog-freshness.ymlruns the whole suite (python3 -m unittest --durations 50) on a runner without bun 1.4.3; onlyvalidate.ymlprovisioned it, so the scheduled and the manual freshness run failed atRealBunTestsand the vendor-definition tests and itsproposejob never startedbun --versionis 1.4.3,AGENT_FRONTMATTER_BUNis exported, andtests.test_adoption_statusunderGITHUB_ACTIONS=trueis 215 OK with it and FAILED (failures=4, errors=1) without it)test_every_job_that_runs_the_suite_provisions_the_pinned_bun_before_it(fails at jobfreshness:0 != 1; it reads every workflow, finds each job whose unittest step reaches the module and holds the step, the version and the checksum equal tovalidate.yml's) andtest_the_reader_tells_the_suite_runners_from_the_named_modulestests.test_agent_drift_recordguards its citations)The mutants of the step and of the reader: 13 of 13 killed (
receipts/mutants-freshness-bun-13.txt). The workflow was not dispatched: itsproposejob force-creates an evidence branch and opens a pull request;receipts/freshness-bun-local-check.txtis the local workflow check. The decision record has a section for this review.Checks at
eecd506f2(idle-class, read at that head):python3 scripts/validate.py --registration-base 77cc7ef42 --registration-before "<4e3780b10> <80f552245> <fb968aff6> <2723f8fd0>"exit 0 (11,913 files hashed); the 70 modules that name a changed file, one process each: 4,555 tests, 68 exit 0 and two that fail for reasons outside this change:tests.test_claude_usage_metrics(prometheus_clientis not installed on this host) andtests.test_token_e2e_grader(a test clone hard-links across devices);tests.test_runtime_worker_openhands_push_gate127 OK on the committed tree (the commit changes no read),tests.test_adoption_status215 OK with bun 1.4.3 underGITHUB_ACTIONS=true,tests.test_agent_drift_record3,tests.test_workflow_hardening118,tests.test_registry_rows25, the fourtests.test_catalog_freshness_*OK; gitleaks on the commit: no leaks; the privacy scan of the lines added since4e3780b10: 0 account, host, home-path, e-mail, non-ASCII or token hits.Follow-ups
31d377e3c(CHANGES_REQUESTED on one P2, the inventory count, answered by80f552245; verdict filefix5-pr1062-31d377e3c/GPT-VERDICT-31d377e3c.md, sha2564e25aa00...): seven follow-ups of the family of the read ofb3da64d76below, with the locators of that head (launcher_resolutionatscripts/adoption_status.py:1487, the default root and launcher at:1496-1497); the PATH-shim P2 stays previously adjudicated; triggers as below.80f552245(ACK, no blockers; verdict filefix6-pr1062-80f552245/GPT-VERDICT-80f552245.md, sha256cddc80f1...; the micro changes the monitoring record, its index and two explanatory records, no production code): eight follow-ups, each with its trigger, none changing code. (1) P3, bind an immutablesource_committo the new observation: the delta'ssource_commitis null and its derivation saysinput commit HEAD; trigger: the next advisory-record append, citing the native implementation at that pin (the published row is preserved; the rebased deltas are null too, derived from a local commit that is not in the published history). (2) P3, current locators (launcher_resolutionat:1487, root and launcher at:1496-1497; the historical1124-1134stays) and the inherited workflow locator: the landing documentation refresh and that record's next edit. (3) P3, the retained edited-read classification wording: the next record edit. (4) P2, previously adjudicated: the Bun PATH-shim selection, keepAGENT_FRONTMATTER_BUN: the next selection change. (5) P3, qualified test citations outside the record guard: the next qualified citation edit. (6) P3, replay, refusal and recovery boundaries: a later pull request re-derives its cumulative delta on the integration tree, as the rebase above did; installer link replacement and non-default Claude homes stay in the installer pull request. (7) P3, the missing captures and original measurement inputs: the next packet. (8) P3 hygiene, the historical hostname text (58 occurrences, 57 in complete changed files, none added): the registry hygiene change.b3da64d76(CHANGES_REQUESTED on this description only; verdict filefix4-pr1062-b3da64d76/GPT-VERDICT-b3da64d76.md, sha2562f8674ec...; the precheck confirmed independently): seven follow-ups, none changing code in this round except where stated.tools/adoption/host-baseline.md:221still gives1521-1531as the current range (Astra:1471-1481atb3da64d76; this commit adds 12 lines tofind_bun, solauncher_resolutionis now atscripts/adoption_status.py:1483, the default root at:1492and the launcher at:1493,1483-1493); keep the historical1124-1134. No test binds it. Trigger: the landing documentation refresh.source_line_move_onlyand its scope says the line was edited: describe the retained class and the scope at the next record edit.find_bunstill selects the firstPATHexecutable (now an absolute path) and invokes it with a scratchHOME; this commit changes how the selection is expressed, not which bun is selected. Keep the pinned binary configured inAGENT_FRONTMATTER_BUN; the separately scoped selection change resolves the pinned install or requires the override. Trigger: the next change to the selection.tests/test_agent_drift_record.pyrecognizes a bare backticked test name, not a class-qualified one (the citations of the design round and of the precheck round are qualified); extend it and show that a nonexistent qualified method fails it. Trigger: the next record edit that cites one.install_claude_profile.py: default home, equal symlink targets skipped,copy2over a link) is fixed only in its own authorized pull request.sub-agents.mdpage, theoven-sh/[email protected]source, release archive andSHASUMS256.txt(and the mise source behind the active-version observation),openai/[email protected]atscripts/adoption_status.py:1007, the installed-client intervals other than theoislice, the full 16,428-text run and the private oracle and mutation drivers, the review captures9bb830a1and27fba06d, and the per-file comparison script cited above. Supply them with their recorded hashes when assembling the next packet.2723f8fd(ACK, no blockers; verdict filerebase1-pr1062-2723f8fd0/GPT-VERDICT-2723f8fd0.md, sha25614a0ca53...; it reproduced the advisory derivation: 351 = 344 + 3 + 4 with exact locations; the delta index's stable patch-id differs only because main's Credential inventory: github-review-app key file and a --from-file custody mode #1057 entry now sits before ours, a context change). Eight follow-ups, six of them the ones above; triggers as above:tools/adoption/host-baseline.md:221(1521-1531, now1468-1478) and the record's historical range: the landing documentation refresh.find_bunchange.UPSTREAM-SRCcaptures, the dated subagents page and the complete measurement inputs: the next packet.CLAUDE_CONFIG_DIR, link replacement; manual recovery for a non-default Claude home) and the snapshot boundary (the check is one observation, not protection against edits after it): the separately authorized installer pull request.docs/decisions/2026-10-11-code-scanning-repo-level.md:53citesvalidate.yml:251for the scanner action, which is at:274(already stale at the base): that record's next edit.fb968aff6(PASS_WITH_P3,research-cc-role-20261008/pr1062-claude-delta-fb968aff.md, sha2569bb830a1...; no new reproduced P1; G1 measured on the 2.1.296 client: one BOM is served and two are not, as the test says):PATHand mise route does not work in a scratch home (measured:env -i ... HOME=<scratch> ~/.local/share/mise/shims/bun --versiongives 1.4.2, and mise installs into the scratch home), so the shim route resolves the wrong version and the check reports null (safe). Trigger: the next change tofind_bun: resolve the pinned install (for examplemise where [email protected]) or requireAGENT_FRONTMATTER_BUN, and never run the shim in a scratch home. Host action (the command center): setAGENT_FRONTMATTER_BUNfor the host's drift-check runs; the record now says thatmise installdoes not change the active version.tests/test_agent_drift_record.py) skips class-qualified names. Trigger: the next record edit that cites a class-qualified test.:1377insource_line_move_only, with a scope that says it was edited (the same as Astra's follow-up 3).fb968aff6(ACK at 09:32:45Z, no remaining blocker; verdict filefix3-pr1062-fb968aff6/GPT-VERDICT-fb968aff6.md, sha25627fba06d...): six P3 follow-ups, each with its trigger:tools/adoption/host-baseline.mdL221 and the decision record (L114) still give the current launcher range as:1521-1531; at this headlauncher_resolutionis atscripts/adoption_status.py:1468and the default root at:1477-1478. No test binds the locator. Trigger: the landing documentation refresh: change the current range to1468-1478and keep the historical1124-1134.source_line_move_onlyand itsscopesays the line was edited. Trigger: the next record edit: say that the classification is retained and its scope distinguishes the edited line.CLAUDE_CONFIG_DIR, links) and the stale practice and currency notes stay on their triggers (the installer bullet and the practice-page andtest_currency_due.pybullets below); manual recovery for a non-default Claude home still needs explicit guidance..claude/skills/claude-native-practice/reference/workers.mdL13, "four host copies differ") is stale: 11 of 11 are equal and this check reports drift. The page is pinned by the skills manifest, so the correction belongs to the next re-pin of the practice skill (the practice-currency change, Bring the Claude native-practice skill from client 2.1.295 to 2.1.296 with a source-cited delta (CPA-V-09) #1053, is the nearest)..claude/agents) and plugin-provided definitions are not read: the check covers the user scope, where the installer writes.tools/adoption/install_claude_profile.py) is not a clean recovery path; the command center ruled that it is its own small pull request after this one lands:install_agentswrites under~/.claude/agentsand not underCLAUDE_CONFIG_DIR; its equality test (L178-L179) follows a link, so a linked copy with equal bytes is skipped and its drift never clears; andshutil.copy2over a link writes through it (python/cpython@cbc944f4bc59Lib/shutil.pyL262). That pull request:not dest.is_symlink()before writing, remove before copy,install_agentshonouringCLAUDE_CONFIG_DIR, a test that fails before. Recovery by hand until then: remove the stale, linked or unlisted file, then rerun the installer.catalogs/foundation/upstream-surface-dispositions.json, 20 on 18 lines ofmanifests/evidence.json, 2 intools/adoption/host-baseline.md); none is added here. A hygiene pull request owns them (the registry change is separately authorized).UPSTREAM-SRCcaptures of the upstream files it cites and a dated capture of the subagents documentation page (the Claude and CPython files were read at their local mirror commits, which the packet did not carry; the page is unmirrored). The mutation and client-oracle drivers stay in the author's private record; the oracle's inputs and the client's answers are committed (tests/fixtures/agent_frontmatter/client-oracle.json) and replayed by the tests.tests/test_currency_due.pyciteadoption_status.pyline ranges (1282-1344and others) that were already off atc63c83a82; unrelated to this change, they are listed for the next edit of that file.VENDOR_HOST_AGENTSis a list of four names: a vendor that installs another definition shows as drift until it is listed (the decision record's overturn conditions).🤖 Generated with Claude Code