From 6c5b4f5cdfcbfc5f50848b78d7aab9d84dd826f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Wed, 23 Sep 2026 05:36:07 +0200 Subject: [PATCH] fix(tooling): the string-payload ratchet scanned nothing under a dot-directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `collect_inventory` filtered on `path.parts` — the ABSOLUTE path — so any checkout living under a dot-prefixed directory skipped every file. Agent worktrees live at `.claude/worktrees/agent-/`, so for a growing share of the people who run this gate it scanned 0 files and found 0 sites. That is not a quiet failure. Finding nothing makes every baseline row read "baseline 349, found 0", i.e. "all of these were converted", and the failure text then says: Run: python3 scripts/string_payload_access_inventory.py --write-baseline Doing what the error says would commit an all-zero baseline. The ratchet would be satisfied forever and could never catch a regression again, and the diff would look like a legitimate "record the progress" commit in review. Three changes: - filter on the path RELATIVE to the repo root (`rel.parts`), which is the thing the filter was always meant to test; - refuse to report a verdict after scanning zero files, with an explicit "do NOT run --write-baseline" — a scanner that looked at nothing must not be able to produce a clean bill of health (CLAUDE.md's fourth way a gate cannot fail); - self-test the dot-directory case. The existing fixture plants a synthetic crate in a tempdir and asserts files_scanned == 1, which is the right shape but cannot catch this, because `/var/folders/...` has no dot component. The new fixture plants the same tree under `.agentdir/` and asserts both the file count and the findings. Measured before/after in a dot-named directory: before, "found 0" for every row plus the --write-baseline instruction; after, 4056 files scanned, 393 inline offsets and 14 reader helpers, exit 0. CI was never affected — runners check out to /home/runner/work/perry/perry. Diagnosis by the turnloop lane, which hit it in an agent worktree. --- .../11082-string-payload-scanner-dotdir.md | 3 ++ scripts/string_payload_access_inventory.py | 46 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 changelog.d/11082-string-payload-scanner-dotdir.md diff --git a/changelog.d/11082-string-payload-scanner-dotdir.md b/changelog.d/11082-string-payload-scanner-dotdir.md new file mode 100644 index 0000000000..4eddf03aa8 --- /dev/null +++ b/changelog.d/11082-string-payload-scanner-dotdir.md @@ -0,0 +1,3 @@ +### Fixed + +- `scripts/string_payload_access_inventory.py` scanned **zero files** when run from a checkout under any dot-prefixed directory — which is where every agent worktree lives (`.claude/worktrees/agent-/`). The skip filter tested `path.parts`, the ABSOLUTE path, so `.claude` matched `part.startswith(".")` and every source file was skipped. The gate then reported each baseline row as `found 0` and printed `Run: … --write-baseline`; following that instruction would have written an all-zero baseline and left the ratchet permanently satisfied. The filter now tests the path relative to the repo root, a scan of zero files fails loudly instead of returning a verdict, and `--self-test` covers a checkout under a dot-named parent (a tempdir alone could not catch this, since `/var/folders/…` has no dot component). CI was never affected: runners check out to `/home/runner/work/perry/perry`. (#11082) diff --git a/scripts/string_payload_access_inventory.py b/scripts/string_payload_access_inventory.py index f0056fc867..1aa1140ad5 100755 --- a/scripts/string_payload_access_inventory.py +++ b/scripts/string_payload_access_inventory.py @@ -212,8 +212,15 @@ def collect_inventory(root: Path = REPO_ROOT) -> tuple[list[Finding], int]: for crate_dir in crate_dirs(root): crate = crate_dir.name for path in sorted(crate_dir.rglob("*.rs")): - rel_path = path.relative_to(root).as_posix() - if any(part.startswith(".") or part == "target" for part in path.parts): + rel = path.relative_to(root) + rel_path = rel.as_posix() + # Filter on the path RELATIVE to the repo root. `path.parts` is + # absolute, so a checkout living under any dot-prefixed directory + # -- `.claude/worktrees/agent-/` is where agents run -- matched + # `part.startswith(".")` on every file and skipped the entire + # workspace. The scan then found nothing and the gate reported each + # baseline row as "found 0", i.e. "everything was converted". + if any(part.startswith(".") or part == "target" for part in rel.parts): continue files_scanned += 1 text = path.read_text(encoding="utf-8") @@ -359,6 +366,30 @@ def expect(condition: bool, message: str) -> None: source.write_text(planted, encoding="utf-8") discovered, files_scanned = collect_inventory(temp_root) expect(files_scanned == 1, "synthetic crate source was not scanned exactly once") + + # The same tree, one level under a DOT-PREFIXED directory. Agents run + # from `.claude/worktrees/agent-/`, and the filter used to test the + # ABSOLUTE path, so every file was skipped and the scan silently + # returned nothing. A tempdir alone cannot catch this: `/var/folders/...` + # has no dot component. + dot_root = temp_root / ".agentdir" / "checkout" + dot_crate = dot_root / "crates" / "synthetic-crate" + (dot_crate / "src").mkdir(parents=True) + (dot_crate / "Cargo.toml").write_text( + '[package]\nname = "synthetic-crate"\nversion = "0.0.0"\n', + encoding="utf-8", + ) + (dot_crate / "src" / "lib.rs").write_text(planted, encoding="utf-8") + dot_found, dot_scanned = collect_inventory(dot_root) + expect( + dot_scanned == 1, + "a checkout under a dot-prefixed directory scanned no files " + "(the filter is testing the absolute path again)", + ) + expect( + counts_for(dot_found) == counts_for(findings), + "a checkout under a dot-prefixed directory lost findings", + ) expect( counts_for(discovered) == counts_for(findings), "filesystem inventory disagreed with direct source scanning", @@ -403,6 +434,17 @@ def main(argv: list[str] | None = None) -> int: return run_self_tests() findings, files_scanned = collect_inventory() + # A scan of zero files is not a clean tree, it is a broken scan. Without + # this, every baseline row reads "found 0" and the failure text invites + # `--write-baseline`, which would zero the ratchet and satisfy it forever. + if files_scanned == 0: + print( + "string-payload access inventory: SCANNED NO FILES -- this is a broken " + "scan, not a converted tree. Do NOT run --write-baseline. Check that " + "crates/ exists under the repo root being scanned.", + file=sys.stderr, + ) + return 1 actual = counts_for(findings) if args.write_baseline: write_baseline(args.baseline, actual)