[BUG] audit --ci walks the whole target root (session transcripts under ~/.claude, ~/.codex): minutes of CPU and false content-integrity findings
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.8k
- Forks
- 362
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 132
Description
Describe the bug
apm audit --ci walks the entire root of every dedicated target (.claude/, .codex/, .cursor/, ...) for the hidden-Unicode signal of content-integrity. At user scope those roots are not deploy trees: ~/.claude/ holds projects/**/*.jsonl session transcripts and history.jsonl, ~/.codex/ holds session logs. APM never deploys anything there, yet the audit regex-scans every byte of it on every run.
Two consequences on a normal developer machine (783 MB under ~/.claude, 4.9 GB under ~/.codex):
- Time. The audit takes ~2m30s of CPU (
sre_searchoverTextIOWrapper.read, sampled). Anything that runsapm audit --ciwith a bounded deadline times out. - False positives.
content-integrityfailed with 22 "critical" findings, all in.claude/history.jsonland.claude/projects/**/*.jsonl. None of those files is deployed, none is in the lockfile, andapm audit --stripis not a sane remedy for a session transcript.--ciexits non-zero for content the audit cannot own.
Cause: _scan_deployed_trees (src/apm_cli/security/file_scanner.py) derives its walk roots from install_governance(), which by design returns the whole root for dedicated targets ("Dedicated target roots govern their full subtree") and only partitions the shared .agents/ root by primitive subdirectory. That is the right scope for ownership tests, but SecurityGate.scan_files then os.walks the whole root with no pruning, no size cap, and no extension filter. Introduced by #2381 (fix for #2379), which widened the scan from deployed_files to the governed tree so that unrecorded deployed files are covered. The widening is correct; the boundary it picked is one level too coarse.
Still present on main (4aaa73cd): file_scanner.py and _check_content_integrity are unchanged since #2381; gate.py only gained __pycache__ exclusions. No open issue or PR covers this.
To Reproduce
- Have a global APM workspace (
~/.apm/apm.yml+ lockfile) with theclaudetarget and a few skills installed. - Use Claude Code or Codex for a while so
~/.claude/projects/and~/.codex/hold transcripts (or seed them:mkdir -p ~/.claude/projects/x && head -c 500M /dev/urandom | base64 > ~/.claude/projects/x/s.jsonl). cd ~/.apm && time apm audit --ci --format json
Observed: minutes of wall time; content-integrity reports findings under .claude/projects/... and .claude/history.jsonl.
Expected behavior
The hidden-Unicode walk covers each target's primitive deploy directories and generated files (.claude/skills/, .claude/agents/, .claude/commands/, .claude/hooks/, .claude/rules/, .codex/hooks.json, .agents/skills/, ...), exactly the way .agents/ is already partitioned. That keeps the #2379 guarantee (an unrecorded file under a deploy directory is still scanned) while leaving session data alone. Lockfile-claimed paths outside those directories (for example .claude/settings.json) stay covered by the lockfile-scoped scan, which is unioned with the tree walk today.
With that change on the same machine: 5s CPU, content-integrity passes, drift results identical.
Environment
- OS: macOS 26 (Darwin 25.6.0), arm64
- Python Version: 3.12
- APM Version: 0.29.0 (
656f3d6), also verified againstmain4aaa73cd
Logs
$ cd ~/.apm && time apm audit --ci --format json
...
138.26s user 6.41s system 92% cpu 2:37.16 total
$ ps -o etime,%cpu,command -p <pid>
01:52 98.8 .../apm audit --ci --format json
# sampled: sre_search <- _io_TextIOWrapper_read, no open FDs besides transcripts
Before/after on the same workspace, apm audit --ci --format json:
| wall | CPU | content-integrity | |
|---|---|---|---|
| main | 2m37s | 138s | 22 findings, all .claude/projects/**/*.jsonl + .claude/history.jsonl |
| bounded walk | 25s (5s idle machine) | 5s | 0 findings |
Additional context
I have the fix ready (small: a bounded= keyword on install_governance() that applies the existing .agents/ partitioning to every root, used only by _scan_deployed_trees; ownership/prune callers unchanged; tests + docs). Will open the PR against this issue. Happy to adjust the boundary if maintainers prefer a different one (for example an explicit skip list per target instead of primitive partitioning).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/apm_cli/security/file_scanner.py, especially _scan_deployed_trees and SecurityGate.scan_files, then trace install_governance() and _check_content_integrity. Review the existing ownership and scan tests before changing only the deployed-tree scan boundary. Done means primitive deploy directories and generated files remain covered, session transcripts are skipped, lockfile-scoped paths remain covered, and the reported audit findings and runtime are corrected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100