The nightly sweep’s dry run said it would commit nothing. It was holding a whole 190-file vault repo over one grep.
What the sweep was looking at
A live session’s subagent had grepped one of its own dirty files while researching. It was pure inspection, no edit. The dry run held the whole repo anyway.
The ownership check in sweep_attribution._owns() was a naive path-substring test. It asked whether a session’s shell command contained the dirty file’s path, and any mention counted as a claim. Reading a path is not a claim on it, but the check could not tell the difference.
A pipe inside the pattern
The grep’s regex argument contained a literal |. The naive path-substring check could not tell that from a real ownership claim. A naive split on | would also misparse it as a second pipeline stage and make the whole line look non-inspecting.
The fix: drop the statements that only look
_owns() now runs each Bash or PowerShell command through _mutating_text():
It splits the command into statements on &&, ||, ; and newline.
It tokenizes each statement with shlex, which is quote-aware, so a | inside a quoted pattern is not mistaken for a pipe.
It splits on a bare | into pipeline stages.
It drops any statement whose every stage is a pure-inspection verb: grep, cat, ls, awk, sort, tr, read-only git subcommands like diff, show, log and status, and a few more.
Only the text that survives gets searched for the dirty path. sed -i, mv, rm, git mv and running a script still count as ownership. A statement shlex cannot parse, such as one with an unbalanced quote, is kept as is. The safe direction is to let it count as a claim rather than exempt it.
Checking it
I reproduced the failure against a session’s real transcripts and the vault commit that cleaned up the false hold, and commit_all flipped back to True. I added two regression tests to the sweep attribution test file and confirmed both fail without the fix: a read-only grep no longer holds the repo, and sed -i still does.
Now a read-only grep no longer holds the repo, and sed -i still does.
AI Skills
Use this lesson with the AI assistant you already use
A nightly sweep held an entire 190-file repository from being auto-committed because a subagent's read-only search happened to name a file that was independently dirty. The ownership check counted any mention of a path in a shell command as a claim on it, without distinguishing reading from changing.
Paste the prompt, share only the context needed to answer it, and treat the result as a draft for your review. Do not include confidential information or let an AI assistant make changes without your approval.
Optional: for a visual report and saved memory, run /dxdev first.
Don’t have it? Get it at dxdev.com/skills/dxdev. The prompt works without it.
dxdev LESSON · paste into your AI coding agent
LESSON: Reading a File Is Not a Claim on It: Ownership Checks Must Distinguish Inspection From Mutation
SOURCE: dxdev.com/blog/2026-09-26_a-read-only-search-claimed-190-files
WHAT HAPPENED: An automated sweep that commits a session's own changes held an entire 190-file repository because its ownership check treated any mention of a file path in a shell command as a claim on that file, with no distinction between reading it and changing it. A read-only search run by an unrelated subagent happened to name a file that was independently dirty from real edits made elsewhere, and a naive command parser made the false match worse by splitting on a pipe character that actually sat inside a quoted search pattern. The fix tokenizes each shell command properly, splits it into statements and pipeline stages, and drops any statement made entirely of inspection verbs (search, list, view, status, diff) before checking the remainder for a claim on the dirty path.
THE RULE: Any system that infers ownership, authorship, or responsibility from command history or logs must classify each command as inspection or mutation before using it as evidence; a mention of a path is not a claim on it unless the command actually changed that path.
CHECK MY CODE, then report PASS or FAIL with file:line for each:
1. Any ownership, authorship, or attribution logic that treats a command mentioning a path the same as a command that modified that path.
2. Any command-string parsing that splits on a special character (a pipe, a quote, a separator) without first tokenizing quote-aware, risking a character inside a quoted argument being misread as shell syntax.
3. Any automated commit, sweep, or merge decision whose safety depends on an ownership or attribution signal that has not been tested against a genuinely ambiguous real case (a read-only action naming a file someone else changed).
THEN PRINT: a table (check, PASS/FAIL, evidence, fix) + a verdict (applies / partially / OUT_OF_SCOPE / no) + the single most important next action.