opencode icon indicating copy to clipboard operation
opencode copied to clipboard

[BUG] Snapshot module ignores 'watcher.ignore' config, causing excessive disk usage and performance degradation

Open vianb opened this issue 14 hours ago • 1 comments

Description

The Snapshot module (packages/opencode/src/snapshot/index.ts), which is responsible for tracking file changes and generating session diffs, completely ignores the watcher.ignore configuration settings.

When Snapshot.track() is called, it executes:

await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`...

This command blindly adds all files in the workspace to the internal snapshot repository, regardless of whether they are listed in watcher.ignore or standard ignore patterns.

Impact

  1. Disk Space Exhaustion: If a project contains large ignored directories (e.g., data/, venv/, build artifacts), OpenCode tracks them internally. This leads to massive files accumulating in ~/.local/share/opencode/storage/session_diff/ and ~/.local/share/opencode/snapshot/.
  2. Performance degradation: High CPU and I/O usage during every agent step as git add processes large ignored files.
  3. Privacy/Security: Files explicitly ignored by the user (potentially containing sensitive data) are inadvertently tracked in the internal snapshot history.

Reproduction

  1. Create a project with a large directory (e.g., data/ with 1GB+ files).
  2. Configure opencode.json to ignore it:
    { "watcher": { "ignore": ["data/**"] } }
    
  3. Start a session and make the agent perform a task.
  4. Observe ~/.local/share/opencode/snapshot/ growing and git processes consuming CPU, despite the ignore rule.

Code Location

packages/opencode/src/snapshot/index.ts

Proposed Fix (Patch)

The following patch ensures that Snapshot.track() respects both standard ignore patterns and user-defined watcher.ignore rules by updating the internal git repository's info/exclude and cleaning the index.

--- packages/opencode/src/snapshot/index.ts.orig
+++ packages/opencode/src/snapshot/index.ts
@@ -6,6 +6,7 @@
 import z from "zod"
 import { Config } from "../config/config"
 import { Instance } from "../project/instance"
+import { FileIgnore } from "../file/ignore"
 
 export namespace Snapshot {
   const log = Log.create({ service: "snapshot" })
@@ -28,6 +29,31 @@
       await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
       log.info("initialized")
     }
+
+    const ignores = [...FileIgnore.PATTERNS, ...(cfg.watcher?.ignore ?? [])]
+    const infoDir = path.join(git, "info")
+    await fs.mkdir(infoDir, { recursive: true })
+    await Bun.write(path.join(infoDir, "exclude"), ignores.join("\n"))
+
+    const ignoredFiles = await $`git --git-dir ${git} --work-tree ${Instance.worktree} ls-files -i --exclude-standard`
+      .quiet()
+      .cwd(Instance.directory)
+      .nothrow()
+      .text()
+
+    if (ignoredFiles.trim()) {
+      await $`git --git-dir ${git} --work-tree ${Instance.worktree} ls-files -i --exclude-standard -z`
+        .quiet()
+        .cwd(Instance.directory)
+        .nothrow()
+        .pipe(
+          $`xargs -0 git --git-dir ${git} --work-tree ${Instance.worktree} rm --cached -r --ignore-unmatch`
+            .quiet()
+            .cwd(Instance.directory)
+            .nothrow(),
+        )
+    }
+
     await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
     const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
       .quiet()

vianb avatar Jan 16 '26 13:01 vianb