opencode
opencode copied to clipboard
[BUG] Snapshot module ignores 'watcher.ignore' config, causing excessive disk usage and performance degradation
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
-
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/. -
Performance degradation: High CPU and I/O usage during every agent step as
git addprocesses large ignored files. - Privacy/Security: Files explicitly ignored by the user (potentially containing sensitive data) are inadvertently tracked in the internal snapshot history.
Reproduction
- Create a project with a large directory (e.g.,
data/with 1GB+ files). - Configure
opencode.jsonto ignore it:{ "watcher": { "ignore": ["data/**"] } } - Start a session and make the agent perform a task.
- Observe
~/.local/share/opencode/snapshot/growing andgitprocesses 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()