feat(sandbox): auto-save sandbox work to the remote - #5400
Open
pedrofrxncx wants to merge 2 commits into
Open
Conversation
added 2 commits
July 29, 2026 18:50
The daemon only synced the working tree from shutdown(), so any ungraceful exit — SIGKILL on pod eviction, OOM, node loss — took the agent's work with it. Add a 30s loop that commits and pushes work in progress. Reuses publish()'s local half (split out as stageAndCommit + ensureOriginPushable) so the loop inherits the protected-branch refusal, the invalid-decofile-block skip and credentialed origin setup instead of a second blind add/commit/push. The push uses a new async twin so it never blocks the daemon's single event loop long enough to miss a health probe. On by default; opt out with `git.autoCommit: false` in the daemon config.
Make the file-change signal the primary trigger: every repo write nudges the auto-committer, which saves 5s after the writes settle. The 30s interval stays as the floor — the debounce alone can't be trusted (continuous writing keeps resetting it, fs.watch has gaps, and `bash` commits produce no file event). A trailing debounce rather than a save per write: one commit+push per file would push every few hundred ms during a tool loop, and a push to a user's repo can fan out into their CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The daemon only synced the sandbox working tree in
shutdown(). Any ungraceful exit — SIGKILL on pod eviction, OOM, node loss, grace period elapsing before the push finishes — took the agent's work with it.This adds
AutoCommitter(daemon/git/auto-commit.ts): every 30s, if the tree is dirty or the branch has unpushed commits, commit and push. On by default, opt out withgit.autoCommit: falsein the daemon config (validated, works viaPUT /_sandbox/configor a tenant-committed.decocms/daemon.json).Reuse over reinvention
publish()is split into its local half and its network half so the loop shares the rules instead of doing a second blindadd/commit/push:stageAndCommit()— staging + commit, incl. the protected-branch refusal, the invalid-decofile-block net (onInvalidBlock: "skip", same as the shutdown sync) and the org-fs.git/info/excludefiltering.ensureOriginPushable()— credentialed origin + the tokenless-GitHub guard.publish()is now those two pluspushBranch(); behavior unchanged.pushBranchAsync()— new: the loop's push goes throughgitAsyncso it never blocks the daemon's single event loop long enough to miss a health probe (Studio tears a sandbox down on one missed probe). Local git calls stay sync, matchingBranchStatusMonitor's existing 3s poll.Behavior notes
origin/<branch>, same reasoning as the shutdown sync. A rejected push is left for the interactive publish to reconcile.BranchStatusMonitor's already-computed meta, so a clean tree costs zero git calls, and boot-dirty dev-server noise doesn't trigger a tick. The commit itself still stages everything dirty (identical to the shutdown sync) — carrying a little noise beats dropping the user's work.unpushed > 0is a trigger too, so commits the agent made itself viabashalso get pushed.shutdown()stops the loop before its own publish so the two can't race forindex.lock.Testing
daemon/git/auto-commit.test.ts:shouldAutoCommitdecision table, plus two real-git tests against a barefile://origin — a dirty tree ends up on the remote branch, and a disabled committer touches nothing.bun test packages/sandbox/→ 878 pass / 0 fail, including the daemon e2e suite booting the rebuilt binary with the loop wired.bun run check,bun run lint(0 errors),bun run fmt,knipclean.Skipped
No Studio-side UI/org-flag to toggle it — the daemon config field is the switch; add a surface when someone asks for one.
Summary by cubic
Auto-save sandbox work to the remote a few seconds after repo writes settle, with a 30s floor, to prevent data loss on ungraceful exits. Enabled by default, never pushes protected branches, and can be disabled with
git.autoCommit: false.New Features
AutoCommitterthat saves on change (5s trailing debounce) and on a 30s interval; commits and pushes when the tree is dirty or has unpushed commits, using async push to keep the daemon responsive.git.autoCommit(validated) viaPUT /_sandbox/configor a tenant.decocms/daemon.json.Refactors
publish()intostageAndCommitandensureOriginPushableto reuse staging rules; addedpushBranchAsyncfor non-blocking pushes.BranchStatusMonitormetadata to avoid extra git calls.Written for commit f474f81. Summary will update on new commits.