gastownhall/beads · error

failed to commit pending changes before sync: %w

Error message

failed to commit pending changes before sync: %w

What it means

Before pulling, Sync auto-commits any dirty working-set changes (e.g. federation metadata like add-peer plus persistent memories, which plain Commit excludes per GH#2455) so DOLT_MERGE cannot wedge with 'cannot merge with uncommitted changes'. This error means that pre-sync commit itself failed for a reason other than 'nothing to commit'.

Source

Thrown at internal/storage/dolt/federation.go:357

// 2. Merge peer's changes (handling conflicts per strategy)
// 3. Push local changes to peer
//
// Returns the sync result including any conflicts encountered.
func (s *DoltStore) Sync(ctx context.Context, peer string, strategy string) (*SyncResult, error) {
	result := &SyncResult{
		Peer:      peer,
		StartTime: time.Now(),
	}

	// GH#2474: match PullFrom — commit pending changes before the merge,
	// INCLUDING config (where kv.memory.* rows live). Plain Commit excludes
	// config (GH#2455), so federation metadata writes such as add-peer plus any
	// persistent memories would otherwise leave the working set dirty and wedge
	// DOLT_MERGE ("cannot merge with uncommitted changes").
	if !s.readOnly {
		if err := s.commitBeforePull(ctx, "auto-commit before sync"); err != nil {
			if !isDoltNothingToCommit(err) {
				result.Error = fmt.Errorf("failed to commit pending changes before sync: %w", err)
				return result, result.Error
			}
		}
	}

	// Step 1: Fetch from peer
	if err := s.Fetch(ctx, peer); err != nil {
		result.Error = fmt.Errorf("fetch failed: %w", err)
		return result, result.Error
	}
	result.Fetched = true

	// Step 2: Get status before merge
	beforeCommit, _ := s.GetCurrentCommit(ctx) // Best effort: empty commit hash means diff won't be logged

	// Step 3: Merge peer's branch
	remoteBranch := fmt.Sprintf("%s/%s", peer, s.branch)
	conflicts, err := s.Merge(ctx, remoteBranch)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause to see why the commit failed
  2. Check for and clear stale dolt locks or concurrent `bd sync` processes (ps / dolt_admin status)
  3. Run the sync again after resolving contention — the auto-commit retriggers
  4. If a prior crashed sync left the working set wedged, inspect `dolt status` and commit or discard changes manually
  5. Ensure the dolt sql-server is healthy and the database directory is writable/non-full

Example fix

// before: blind retry loop
for { store.Sync(ctx, peer, "", nil) }
// after: single-flight sync guard
syncMu.Lock()
defer syncMu.Unlock()
_, err := store.Sync(ctx, peer, "", nil) // auto-commit no longer races
Defensive patterns

Strategy: validation

Validate before calling

// ensure clean, uncontended working set before Sync
if anotherSyncRunning() { return ErrSyncBusy }
_ = store.Commit(ctx, "pre-sync checkpoint") // best-effort commit outside readOnly

Try / catch

result, err := store.Sync(ctx, peer, strategy, opts)
if err != nil && strings.Contains(err.Error(), "commit pending changes before sync") {
    // clear contention, then retry once
    releaseLocks(); retrySync()
}

Prevention

When it happens

Trigger: Sync(ctx, peer, ...) on a writable store calls commitBeforePull(ctx, "auto-commit before sync") and receives a real commit error (SQL failure, lock contention, hook failure) that is not isDoltNothingToCommit.

Common situations: Another process holds the dolt working set or server lock; dirty state from a prior crashed sync; disk-full or DB connection drop mid-commit; concurrent syncs racing on the same database.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/e0f23187fb301162. Report an issue: GitHub.