Skip to content

fix: cross-session file change leak in Files Changed tab - #301

Merged
jeonghun-jj-lee merged 4 commits into
local/amicodefrom
fix/cross-session-diff-leak
Sep 5, 2026
Merged

fix: cross-session file change leak in Files Changed tab#301
jeonghun-jj-lee merged 4 commits into
local/amicodefrom
fix/cross-session-diff-leak

Conversation

@jeonghun-jj-lee

Copy link
Copy Markdown
Contributor

Problem

File edits made in one session leak into the "Files Changed" tab of other sessions sharing the same project folder. A plan-mode session (zero edits) shows file changes from concurrent build-mode sessions.

Reported: harmoniqs/amicode#733 follow-up

Root cause

All sessions share a single shadow git repository per project. Snapshots capture the entire worktree — session isolation is enforced by filtering. Three gaps in the filter chain allowed cross-session contamination:

1. Session.diff() Fallback 1 serves unfiltered summary.diffs (primary)

computeDiff() (summary.ts) runs snapshot.diffFull(T1, T2) — an unfiltered snapshot diff that captures ALL worktree changes, including other sessions' edits. The result is stored in message.summary.diffs.

When the client fetches GET /session/{id}/diff, plan-mode sessions have zero tool filediff metadata, so agentFilesAbsolute.size === 0, the filtered primary path is skipped, and Fallback 1 reads the contaminated summary.diffs and returns them.

2. computeDiff stores contaminated data (defense-in-depth)

Even for sessions WITH edits, computeDiff stored the full unfiltered diff. The primary path filtered at query time, but the stored data was dirty.

3. keepPreviousData on session switch (client-side)

When switching tabs, TanStack Query served the old session's diffs as placeholder data for the new session, briefly showing wrong files.

Fix (4 commits)

Commit Layer What
fix(session) Server Short-circuit Session.diff() when snapshots exist but no tools ran — return [] instead of falling through to Fallback 1
fix(summary) Server Filter computeDiff by agent-touched files: filediff-tracked → filter; bash-only → full diff; no tools → empty
fix(app) Client Scope placeholderData to the same session ID — intra-session refetches still smooth, inter-session switches drop stale data
chore(i18n) Cleanup Add missing session.exportTrace key to all locale files (pre-existing parity failure)

Testing

  • New regression test: plan-mode session with contaminated summary.diffs → endpoint returns []
  • All existing session-diff-scoped tests pass (8/8)
  • snapshot-tool-race test passes (bash-tool sessions still work)
  • Full typecheck passes (30/30 packages)
  • Full server test suite passes (595/595)
  • App test suite: 956/956 pass (4 pre-existing SolidJS/bun env failures on macOS, pass in CI)

…dits

When a session has step-start/step-finish snapshots but zero completed
tool parts with filediff metadata (e.g. a plan-only session), return []
immediately instead of falling through to Fallback 1.

Fallback 1 reads message.summary.diffs, which are populated by
computeDiff — an unfiltered snapshot diff that captures ALL worktree
changes between the two tree hashes, including edits from other
concurrent sessions. This was the primary cross-session contamination
vector for the Files Changed tab: a plan-mode session would show another
session's file edits because the snapshot system uses a single shadow
git repo per project.

The early return is safe: if snapshots exist (from is defined), the
session is 'modern' — Fallback 1 was designed for legacy sessions that
predate the snapshot infrastructure, not as a supplement to the primary
path.

Closes harmoniqs/amicode#733 follow-up (computeDiff filtering).
Defense-in-depth: computeDiff now extracts agent-touched files from
completed edit/write/patch/apply_patch tool parts and filters the
snapshot diff to only those files, preventing cross-session changes
from being stored in message.summary.diffs in the first place.

Three cases:
- Agent files exist (filediff metadata) → filter to those files only
- Tools ran but none have filediff (e.g. bash) → return full diff
  (can't determine which files the agent touched)
- No tools at all (plan-only) → return empty (all changes are external)

This is the follow-up explicitly called out in the #733 commit message:
'a follow-up in the fork will add agent-file filtering to computeDiff
to fix the leak at the source'.
Replace keepPreviousData with a session-scoped placeholder function
that only preserves data from the same sessionID. This prevents the
cross-session flash: when switching from Session A to Session B, the
diff query no longer serves Session A's diffs as placeholder data in
Session B's review panel.

Intra-session refetches (diff_version bumps during a turn) still get
smooth transitions — the placeholder is kept because the sessionID
matches. Only inter-session switches (params.id change) drop the
stale data, falling to the tool-metadata fallback which is correctly
scoped to the new session's messages.

Applied to both sessionDiffQuery and touchedFilesQuery.
The key was added to en.ts in 77d7f8d but missing from all
non-English locale files, failing the i18n parity test.
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: a9454aee-fcd9-4b4d-9b0c-31393258327f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jeonghun-jj-lee
jeonghun-jj-lee merged commit 424f96b into local/amicode Sep 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant