How the No-Mistakes Branch Sync Service Handles Recovery After a Cancelled Push
When a push is cancelled, the no-mistakes branch sync service preserves the repository state in the database and surfaces a recovery interface that lets users either apply the local worktree or revert to the gate head.
The no-mistakes repository provides a guarded synchronization layer that protects local worktrees from data loss during interrupted Git operations. The branch sync service, implemented primarily in internal/branchsync/sync.go, detects cancellation signals and implements a robust recovery workflow that preserves repository integrity through state persistence and user-driven resolution.
The Recovery Workflow
The recovery process follows a strict sequence from detection to resolution, ensuring that cancelled pushes never leave the repository in an inconsistent state.
Step 1: Guarded Sync Initialization
When a user initiates synchronization via the TUI or CLI, the service creates a State struct in internal/branchsync/sync.go. This structure records the current branch, its upstream remote, and any pending worktree changes. The StartSync logic persists this state to the database before attempting any network operations, creating a restore point that survives process termination.
Step 2: Push Execution and Cancellation Detection
The sync routine calls pushBranch to execute the Git push. If the user cancels the operation (Ctrl-C) or the process receives a termination signal, the underlying exec.CommandContext is killed. The handlePushError function interprets the resulting error as a cancellation rather than a hard failure, triggering the recovery pathway instead of treating it as a terminal error.
Step 3: State Preservation in the Database
Upon detecting cancellation, the service does not discard the partially-pushed worktree. Instead, it stores the current sync state in the database via internal/db/run.go. The runs.custody_returned_at field remains nil, marking the run as parked. This preservation mechanism ensures the daemon can later reconstruct the exact state of the repository at the moment of interruption, including the relationship between the local head and the gate head.
Step 4: Recovery Interface and User Decision
When the daemon (managed in internal/daemon/manager.go) detects a parked sync—either after a restart or during a status check—it renders a recovery interface via internal/tui/branch_sync.go. The UI presents two options: press u to apply the local worktree (keep changes) or esc to revert to the gate head (discard changes). This interactive decision point prevents automatic data loss while giving the user explicit control over the outcome.
Step 5: Finalizing Recovery
If the user selects apply, the service invokes applyRecovery in internal/branchsync/sync.go, which executes git reset --hard <saved-head> to align the gate with the local worktree. If the user selects revert, revertRecovery checks out the saved gate head, discarding local changes. Both paths conclude by clearing the sync state from the database, setting runs.custody_returned_at, and releasing the branch lock that prevented concurrent operations.
Key Implementation Files
The recovery mechanism spans several packages, each responsible for a specific layer of the system:
| File | Responsibility |
|---|---|
internal/branchsync/sync.go |
Core sync logic, State struct definition, StartSync, pushBranch, handlePushError, applyRecovery, and revertRecovery functions. |
internal/branchsync/recover_test.go |
Test suite verifying the recovery flow after various cancellation scenarios. |
internal/cli/sync.go |
CLI command definition and flag parsing for --recover, --apply, and --revert options. |
internal/tui/branch_sync.go |
Interactive UI rendering and key binding handling for the recovery prompt. |
internal/db/run.go |
Database persistence for run state, including the custody_returned_at field used to track parked syncs. |
internal/daemon/manager.go |
Daemon lifecycle management and final cleanup after recovery decisions are applied. |
CLI Recovery Commands
While the TUI provides an interactive recovery flow, the CLI exposes explicit flags for scripting and automation:
# Start a guarded sync (interactive TUI)
no-mistakes sync
# If previously cancelled, recover by keeping local changes:
no-mistakes sync --recover --apply
# Or revert to the gate head, discarding local work:
no-mistakes sync --recover --revert
These commands correspond directly to the applyRecovery and revertRecovery functions, providing non-interactive paths for CI/CD pipelines or terminal-based workflows.
Summary
- The no-mistakes branch sync service treats cancelled pushes as recoverable events, not terminal failures.
- Sync state is persisted to the database via the
Statestruct andrunstable fields before and during network operations. - Cancellation is detected in
handlePushErrorand triggers a parked state rather than cleanup. - Users recover via
internal/tui/branch_sync.go(interactive) orinternal/cli/sync.go(flags) by choosing to apply local work or revert to the gate head. - The daemon in
internal/daemon/manager.gofinalizes recovery by clearing state and releasing locks, ensuring branch consistency.
Frequently Asked Questions
What happens to my local changes if I cancel a push mid-operation?
Your local changes are preserved. The service stores the current state in the database (with custody_returned_at unset) and marks the sync as parked. The worktree remains intact until you explicitly choose to either apply the changes to the gate or revert to the previous gate head through the recovery interface.
How does the service distinguish between a network failure and a user cancellation?
The handlePushError function in internal/branchsync/sync.go inspects the error returned by the Git subprocess. Context cancellation errors (from exec.CommandContext) are interpreted as user-initiated cancellations, while non-cancellation errors (timeouts, auth failures, network errors) are treated as hard failures that do not trigger the recovery workflow.
Can I automate recovery without using the interactive TUI?
Yes. The CLI supports the --recover flag combined with either --apply or --revert in internal/cli/sync.go. Running no-mistakes sync --recover --apply programmatically accepts custody of the local worktree, while --revert restores the gate head, allowing full automation in shell scripts or CI environments.
Where is the sync state stored during the recovery process?
The state is stored in the local database managed by internal/db/run.go. The runs table tracks the sync context, including branch names, commit SHAs, and the custody_returned_at timestamp. This persistence ensures that even if the daemon restarts, it can reconstruct the recovery context from the database without relying on in-memory state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →