Skip to content

fix(core): keep OpenCode data out of filesystem snapshots - #56209

Open
Parker-Fawcett wants to merge 2 commits into
anomalyco:devfrom
Parker-Fawcett:snapshot-self-capture
Open

Parker-Fawcett wants to merge 2 commits into
anomalyco:devfrom
Parker-Fawcett:snapshot-self-capture

Conversation

@Parker-Fawcett

@Parker-Fawcett Parker-Fawcett commented Oct 16, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #55010

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When OpenCode's data directory is inside the snapshotted worktree, for example a Git-tracked $HOME, each capture recorded the snapshot store's own new objects, so the store kept growing. Those paths also came back from Snapshot.files, so a revert could delete live store files. In a local test, reverting one step removed 32 of 34 of them.

The snapshot now works out where global.data sits relative to the worktree, by real path, and:

  • excludes it from capture with an :(exclude,literal) pathspec;
  • removes entries an earlier capture already put in the store's index, since the pathspec would otherwise hide them forever;
  • drops those paths from files, diff, and restore plans, so trees captured before this change can't touch the data directory.

Nothing changes when the data directory is outside the worktree.

How did you verify your code works?

  • Two tests in snapshot.test.ts, with the data dir at <home>/.local/share/opencode, reached through a symlink:
    • The first checks that a one-line edit captures only that file, and that restore leaves the data dir alone.
    • The second checks that trees captured without the exclusion are filtered in files/diff, and that the next capture cleans the index.
    • Both fail on dev. Removing any one part of the fix (exclusion, cleanup, filtering, or the real-path lookup) makes at least one of them fail.
    • Both pass with --rerun-each 10 --randomize and use about 30 git spawns.
  • packages/core: full-suite failures are identical to dev on my machine (30, all from macOS Bash 3.2 and ripgrep in my environment). bun typecheck and root bun run check pass.

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code


Added by the opencode bot (AI-generated).

Related: #51836, #16336, #36093

When OpenCode's data directory is inside the snapshotted worktree, for
example a Git-tracked $HOME, every capture recorded the snapshot store's
own new objects, and a revert could delete live store files. Exclude the
data directory from capture, remove entries an earlier capture already
recorded there, and drop it from files, diff, and restore plans so older
trees stay harmless.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works, and I don't see anything that blocks a merge. I reproduced #55010 through Snapshot.capture and Snapshot.files, with the data directory at <home>/.local/share/opencode inside a Git-tracked home folder and 8 captures after one-line edits. On the base, the store's own entries in each tree grew 22 → 55 → … → 2625, and files returned up to 934 store paths per step. With this change, trees hold only the edited files, and files returns just the edited file. That holds both when the location is the home root and when it's a subdirectory. The new tests in snapshot.test.ts pass here and fail on the base. git.test.ts and bun run typecheck in packages/core pass.

The public API is unchanged: CaptureInput.excludes is an optional field on the internal Git service.

Two small, non-blocking points:

  • Only global.data is excluded. In the same home-folder setup, ~/.cache/opencode and ~/.local/state/opencode can still end up in snapshots and be rewritten by a revert. They don't grow the store the way the data directory did, so leaving them for later is fine.
  • When the data directory is inside the worktree, the stale-entry ls-files runs on every capture. Running it once per store, next to sweepTemporaryIndexes/ensureSnapshotConfig, would also work, since the exclusion keeps the entries from coming back. This only matters for that setup.

Related: #51836, #16336, #36093

@Parker-Fawcett

Copy link
Copy Markdown
Author

Thanks for checking both the home-root and subdirectory cases.

  • Cache and state: agreed. They don't feed back into the store, so I kept this PR to the data directory and can follow up if you want those excluded too.
  • Stale-entry ls-files: every capture writes a temporary index and renames it over the store's index, and when writers race the last rename wins. A cleanup that ran once per process could be lost that way, so it stays in the capture. It only runs when the data directory is inside the worktree, and costs one ls-files per capture there.

🤖 Addressed by Claude Code

Keep dev's early returns and precomputed diff paths in Snapshot.files
and Snapshot.diff, together with this branch's data directory filter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

This branch has not been deployed

No deployments
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.

Snapshot repo bloats to 15 GB in 11.5 h on v2.0.27 (latest): 334k loose objects + tmp_obj_*/tmp_pack_* garbage; maintenance gc ineffective

1 participant