Session summary/diff is worktree-global (not session-scoped) causing cross-session attribution #8338

Open
opened 2026-02-16 18:09:43 -05:00 by yindo · 2 comments
Owner

Originally created by @kyashrathore on GitHub (Feb 2, 2026).

Originally assigned to: @thdxr on GitHub.

TL;DR

Session diffs are filtered by files from patch parts, but both the diff and the patch files come from the same worktree-global snapshots. Filtering by files derived from the same global source is circular and ineffective - concurrent sessions still pollute each other's summaries.

Root Cause

// summary.ts - files comes from patch parts
const files = new Set(
  input.messages
    .flatMap((x) => x.parts)
    .filter((x) => x.type === "patch")
    .flatMap((x) => x.files)  // <-- from Snapshot.patch() = worktree-global
)

// diffs also come from worktree-global snapshots
const diffs = await computeDiff({ messages }).then((x) =>
  x.filter((x) => files.has(x.file))  // <-- filtering is ineffective
)

Both patch.files (from Snapshot.patch()) and computeDiff() (from Snapshot.diffFull()) operate on Instance.worktree. If Session B modifies files during Session A's step, those files appear in Session A's patch and pass the filter.

Proposed Fix

1. Extract files from tool result metadata (session-scoped)

// summary.ts - get files from tool metadata instead of patch parts
const files = new Set(
  input.messages
    .flatMap((x) => x.parts)
    .filter((x): x is MessageV2.ToolPart => x.type === "tool" && x.state.status === "completed")
    .flatMap((x) => {
      if (x.tool === "edit") return [x.state.metadata?.filediff?.file]
      if (x.tool === "write") return [x.state.metadata?.filepath]
      if (x.tool === "bash") return x.state.metadata?.files ?? []
      return []
    })
    .filter(Boolean)
    .map((x) => path.relative(Instance.worktree, x).replaceAll("\\", "/")),
)

2. Add per-tool snapshots to bash tool

Currently bash doesn't track which files it modifies. Add snapshot before/after:

// bash.ts - capture files modified by bash command
import { Snapshot } from "@/snapshot"

async execute(params, ctx) {
  // Snapshot BEFORE bash runs
  const snapshotBefore = await Snapshot.track()

  // ... existing spawn and execution code ...

  // Capture changed files AFTER bash completes
  const changedFiles: string[] = []
  if (snapshotBefore) {
    const patch = await Snapshot.patch(snapshotBefore)
    changedFiles.push(...patch.files)
  }

  return {
    title: params.description,
    metadata: {
      output: /* ... */,
      exit: proc.exitCode,
      description: params.description,
      files: changedFiles,  // <-- track files modified by bash
    },
    output,
  }
}

Remaining Collision Window

After this fix, false attribution only occurs if two sessions run bash commands that modify the exact same file during the exact same snapshot window (milliseconds). This is a dramatically smaller window than the current step-level collision.

Environment

  • Branch: dev
  • Commit: 7b3c82d95c10e7ec364d8b970209dadfdb43e4dc
Originally created by @kyashrathore on GitHub (Feb 2, 2026). Originally assigned to: @thdxr on GitHub. ## TL;DR Session diffs are filtered by files from `patch` parts, but both the diff and the `patch` files come from the same worktree-global snapshots. **Filtering by files derived from the same global source is circular and ineffective** - concurrent sessions still pollute each other's summaries. ## Root Cause ```ts // summary.ts - files comes from patch parts const files = new Set( input.messages .flatMap((x) => x.parts) .filter((x) => x.type === "patch") .flatMap((x) => x.files) // <-- from Snapshot.patch() = worktree-global ) // diffs also come from worktree-global snapshots const diffs = await computeDiff({ messages }).then((x) => x.filter((x) => files.has(x.file)) // <-- filtering is ineffective ) ``` Both `patch.files` (from `Snapshot.patch()`) and `computeDiff()` (from `Snapshot.diffFull()`) operate on `Instance.worktree`. If Session B modifies files during Session A's step, those files appear in Session A's patch and pass the filter. ## Proposed Fix ### 1. Extract files from tool result metadata (session-scoped) ```ts // summary.ts - get files from tool metadata instead of patch parts const files = new Set( input.messages .flatMap((x) => x.parts) .filter((x): x is MessageV2.ToolPart => x.type === "tool" && x.state.status === "completed") .flatMap((x) => { if (x.tool === "edit") return [x.state.metadata?.filediff?.file] if (x.tool === "write") return [x.state.metadata?.filepath] if (x.tool === "bash") return x.state.metadata?.files ?? [] return [] }) .filter(Boolean) .map((x) => path.relative(Instance.worktree, x).replaceAll("\\", "/")), ) ``` ### 2. Add per-tool snapshots to bash tool Currently bash doesn't track which files it modifies. Add snapshot before/after: ```ts // bash.ts - capture files modified by bash command import { Snapshot } from "@/snapshot" async execute(params, ctx) { // Snapshot BEFORE bash runs const snapshotBefore = await Snapshot.track() // ... existing spawn and execution code ... // Capture changed files AFTER bash completes const changedFiles: string[] = [] if (snapshotBefore) { const patch = await Snapshot.patch(snapshotBefore) changedFiles.push(...patch.files) } return { title: params.description, metadata: { output: /* ... */, exit: proc.exitCode, description: params.description, files: changedFiles, // <-- track files modified by bash }, output, } } ``` ## Remaining Collision Window After this fix, false attribution only occurs if two sessions run bash commands that modify **the exact same file** during **the exact same snapshot window** (milliseconds). This is a dramatically smaller window than the current step-level collision. ## Environment - Branch: `dev` - Commit: `7b3c82d95c10e7ec364d8b970209dadfdb43e4dc`
Author
Owner

@github-actions[bot] commented on GitHub (Feb 2, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #7753: One OpenCode session's modified files appear in another session's Modified Files list in the same repo (DIRECT DUPLICATE)
  • #7555: Session Changes / Modified Files show changes from origin/main (RELATED - same worktree-global diff issue)
  • #10716: TUI session_diff tracking not working on Windows - files remain empty (RELATED - affects session_diff mechanism)
  • #5910: Undo previous message incorrectly restores project state (DIRECT DUPLICATE - same snapshot/session scoping issue)
  • #10589: Snapshot .nothrow() causes silent data loss - /undo and /redo revert files to stale content (RELATED - same worktree scope issue)
  • #4251: Concurrent sessions working on different repos interfere each other (DUPLICATE PATTERN - cross-session contamination)

All of these issues stem from the same root architectural problem: session state (snapshots, diffs, modified files) being computed at the Instance.worktree level rather than being properly scoped to individual sessions.

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Feb 2, 2026): This issue might be a duplicate of existing issues. Please check: - #7753: One OpenCode session's modified files appear in another session's `Modified Files` list in the same repo (DIRECT DUPLICATE) - #7555: Session Changes / Modified Files show changes from origin/main (RELATED - same worktree-global diff issue) - #10716: TUI session_diff tracking not working on Windows - files remain empty (RELATED - affects session_diff mechanism) - #5910: Undo previous message incorrectly restores project state (DIRECT DUPLICATE - same snapshot/session scoping issue) - #10589: Snapshot `.nothrow()` causes silent data loss - /undo and /redo revert files to stale content (RELATED - same worktree scope issue) - #4251: Concurrent sessions working on different repos interfere each other (DUPLICATE PATTERN - cross-session contamination) All of these issues stem from the same root architectural problem: session state (snapshots, diffs, modified files) being computed at the `Instance.worktree` level rather than being properly scoped to individual sessions. Feel free to ignore if none of these address your specific case.
Author
Owner

@AndryHTC commented on GitHub (Feb 4, 2026):

Yes. It's critical

@AndryHTC commented on GitHub (Feb 4, 2026): Yes. It's critical
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8338