Skip to content

feat: per-run snapshot-based file-change tracking (#257) - #308

Draft
regnull wants to merge 1 commit into
mainfrom
feat/run-file-changes
Draft

regnull wants to merge 1 commit into
mainfrom
feat/run-file-changes

Conversation

@regnull

@regnull regnull commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Summary

Port OpenCode's MIT-licensed snapshot approach (snapshot/ service) so every run records which workspace files it changed and a unified diff — all without touching the workspace's own .git.

Closes #257.

How It Works

Each workspace gets a dedicated, bare git repository under .openbot/snapshots/<sha256-of-workspace-path>. Before a run, the runner fingerprints the workspace with git add -A && git write-tree against that store; after the run ends (completed / failed / cancelled / undeliverable human question), it captures a second tree and diffs the two with git diff-tree --name-status -r -z (for the file list) and git diff-tree -p (for the unified diff).

The store's index is rebuilt from the worktree on every capture — three concurrent bots editing the same workspace never interleave writes because captures are serialized per-storedir (in-process asyncio.Lock), and git itself provides index.lock for cross-process safety.

What Changed

Layer File Change
New backend/openbot/runtime/snapshot.py SnapshotStore class + store_for_run helper — entire snapshot/diff engine (credit comment links OpenCode MIT source)
New backend/openbot/db/migrations/versions/0023_run_snapshots.py Adds changed_files (JSON) and diff (Text) columns to runs
New backend/tests/test_snapshot.py 7 tests: store init, capture+diff, gitignore/OOB exclusion, deletions, no-change empty diff, full runner wiring with actual tool calls
Modified backend/openbot/db/models.py Run.changed_files + Run.diff mapped columns
Modified backend/openbot/api/schemas.py RunOut.changed_files + RunOut.diff
Modified backend/openbot/runtime/runner.py _snapshot_capture_before, _snapshot_store_after, _snapshot_attach_terminal methods; wired into execute() at start and all terminal paths (completed / cancelled / failed / undeliverable ask_human)
Modified backend/openbot/runtime/cli_agents/run.py CLI agent path now calls _snapshot_attach_terminal instead of _set_status for its terminal status
Modified frontend/src/api/types.ts Run interface gets changed_files?: string[] | null + diff?: string | null
Modified frontend/src/components/RunCard.tsx New "Changed" section: file count, up to 4 filename badges, "+N more", collapsible <details> unified diff

Design Decisions

  • No revert. Several bots share one workspace; reverting one run's snapshot would clobber other bots' edits.
  • Best-effort, never blocking. A failed snapshot capture or diff is logged as a warning and the run proceeds normally — changed_files simply stays None.
  • Gitignore-respecting. The store inherits the workspace's own .gitignore, .git/info/exclude, and global excludes. .openbot is additionally always excluded via the store's info/exclude, so OpenBot state is never recorded as a run's changes.
  • Rename detection disabled. --no-renames on diff-tree matches OpenCode's behavior: one run's delete+recreate of a file shows as delete+add, which is clearer in a per-run audit.
  • Before-tree persists across resumes. A run paused on ask_human re-enters execute() on resume; its original before-tree is keyed in self._snapshot_before so the final diff spans the entire multi-segment run.

Test Results

  • Backend: 848 passed (841 base + 7 new), 30 deselected, 0 failures — ruff check clean
  • Frontend: 349/351 passed — 2 pre-existing failures in electron/main.test.ts (unrelated --root-directory test, also fails on main), tsc --noEmit clean, pnpm lint 0 errors (1 pre-existing set-state-in-effect warning)

Port OpenCode's snapshot approach (MIT-licensed, credit in snapshot.py) to record
which workspace files each run changed and a unified diff, without touching the
workspace's own .git.

- New SnapshotStore (backend/openbot/runtime/snapshot.py): per-workspace
  dedicated git repo under .openbot/snapshots/<hash> — git add -A + write-tree
  before/after each run, diff-tree for changed files + unified diff.
  Concurrent bots share one workspace safely: in-process serialization per store,
  git's own index.lock for cross-process, and .openbot always excluded.
- DB: migration 0023 adds changed_files (JSON) and diff (Text) to runs.
- Runner: _snapshot_capture_before at run start, _snapshot_attach_terminal at
  completion/failure/cancellation; CLI agent path updated too.
- API: RunOut schema exposes changed_files + diff.
- Frontend: RunCard renders a 'Changed' section (file count, filenames, collapsible
  unified diff) when changed_files is non-empty, consistent with existing card style.
- Tests: 7 tests in tests/test_snapshot.py covering store init, capture+diff,
  gitignore/OOB exclusion, no-change empty diff, and full runner wiring with
  actual tool calls.

All values are best-effort: a failed snapshot never blocks the run.
@regnull
regnull force-pushed the feat/run-file-changes branch from 28b48d4 to 46670d6 Compare October 19, 2026 11:22
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.

Record and show each run's file changes

1 participant