Bash diff panel reports another process's file writes as changes made by an unrelated command
Environment
- Claude Code 2.1.278 (Homebrew cask,
/opt/homebrew/Caskroom/claude-code@latest/2.1.278/claude) - macOS, Darwin 25.6.0, Apple Silicon
- Several concurrent
claudesessions sharing one project root
Summary
The file-change panel rendered under a Bash tool call claims the command created or modified files
that were in fact written by a different process. A strictly read-only command (du) is shown
"Created"-ing 34 files it never touched.
The panel has a caveat string for concurrent activity, but it is gated on process-local state, so it
can never fire for a writer in another process — which is the only case that needs it. A second
caption rule means most occurrences render with no explanatory text at all.
Original observation
A read-only Bash call in one session:
du -sh ~/.claude/projects; find ~/.claude/projects -name '.jsonl' | wc -l; du -sh ~/.claude/projects/
rendered:
Created .orca/<run-id>/reviews/W3-codex.json
Created .orca/<run-id>/reviews/W3-codex.round0.json
(what this command changed; a convenience view, not a review or audit of the command)
Both files (mtime 15:55:02) were written by a different Claude Code session sharing the project root.du cannot create files.
Reproduction
Deterministic, ~45s, one session. The writer must not be a descendant of a Claude-tracked
process, so launch it under launchd rather than backgrounding it from the agent's own Bash call.
- Open a session whose project root is
$REPO.
- Start a foreign writer (ppid 1):
launchctl submit -l ccrepro -- /bin/zsh -c \
'for i in $(seq -w 1 45); do perl -e "select(undef,undef,undef,1)"; \
printf "foreign write %s\n" "$i" > '"$REPO"'/ZZ_FOREIGN_$i.txt; done'
- Immediately, in the session, run one strictly read-only command lasting ~40s:
end=$((SECONDS+35)); while [ $SECONDS -lt $end ]; do du -sh $REPO/somedir >/dev/null 2>&1; done
- Look at the panel rendered under step 3.
Expected: no file-change panel. The command created nothing.
Actual:
Created ZZ_FOREIGN_03.txt (+1 -0)
1 +foreign write 03 at 16:57:10
Created ZZ_FOREIGN_04.txt (+1 -0)
... "29 more files changed" (34 total)
Cleanup:
launchctl remove ccrepro
find $REPO -maxdepth 1 -name 'ZZ_*.txt' -delete
Note: launchctl submit implies KeepAlive, so remove the job as soon as the window closes.
Cause
Identifiers below are minified names from the 2.1.278 binary.
There is no writer identity in the system. On macOS the observer is a single recursive fs.watch on
the root (u4o); FSEvents reports paths, not pids. Attribution is therefore inferred from timing
alone, in LN():
let s = [...Jh.values()].filter(g => g.root === e.root); // in-flight commands, THIS process
if (s.length === 0) { e.outsideWrites.add(n); return; } // none in flight → folded into baseline
for (let g of s) {
if (s.length > 1) g.shared = true; // 2+ local commands → hedge
g.paths.add(n); // exactly 1 → asserted as its doing
}
The assumption is sole in-flight command implies sole cause. That holds within one process and
fails as soon as anything else writes under the root.
Jh is a process-local Map. A peer session's command is not in it, and neither is a dev server, a
formatter or a build watcher. So s.length > 1 cannot become true for an out-of-process writer, theshared flag is never set, and the foreign write is attributed to whichever local command is open.
The same process-local test gates the parked-write path in $tt():
let g = [...Jh.values()].some(h => h.root === r.root);
if (s.held.size > 0 && g) n.shared = true;
Two caption strings exist in the renderer:
d= "(what this command changed; a convenience view, not a review or audit of the command)"
— rendered only for the first diff panel in the process (O ??= t, rendered when O === t).
N= "(another command ran in this repository at the same time; a change made by either may show
under either result)" — rendered only when shared is set, i.e. never for a cross-process writer.
So the first misattribution in a session asserts causation outright, and every later one renders as a
bare "Created ..." list with no caption and no hedge.
Impact
- The panel states something factually false about what a command did, with no uncertainty marker.
- It is worse for agents than for humans: a coding agent reading the panel can conclude it created
files it did not create, and act on that — reverting, deleting or rewriting another session's work.
- Concurrent sessions on one repo are a supported and increasingly common workflow, and shared-root
setups (monorepo containers, worktrees) make it routine.
Not established
Four earlier attempts at the reproduction above, in an older session of the same machine and root,
produced no panel at all. The two successful runs were in a fresh session. Something process- or
time-scoped gates whether collected paths ever render — candidates visible in C4o are an
unavailable diff baseline, git check-ignore filtering, a > 5000 path bail, and the macOS timing
gate l4o (grace 30ms, quiet 60ms, capped at 300ms) setting overflow. This was not pinned down and
is not part of the claim above. The cause section stands on its own.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗