gastownhall/beads · error

compact step %q: %w

Error message

compact step %q: %w

What it means

Compact's internal execSQL helper wraps every step (branch creation, checkout, reset, squashed commit, cherry-picks, resets) as 'compact step "<step name>": %w', pinpointing which stage of the squash recipe failed. The wrapper is generic — the underlying driver error and step name together identify the cause. A deferred best-effort cleanup removes the compact-tmp branch if a later step fails after branch creation.

Source

Thrown at internal/storage/versioncontrolops/compact.go:39

//
// conn must be a single database connection (not a pooled *sql.DB) since the
// stored procedures rely on session-scoped state (current branch, working set).
func Compact(ctx context.Context, conn DBConn, initialHash, boundaryHash string, oldCommits int, recentHashes []string) (retErr error) {
	branchCreated := false

	// Best-effort cleanup: if any step fails after creating the temp branch,
	// try to return to main and delete the temp branch so future compactions
	// aren't blocked by a leftover branch.
	defer func() {
		if retErr != nil && branchCreated {
			_, _ = conn.ExecContext(ctx, "CALL DOLT_CHECKOUT('main')")
			_, _ = conn.ExecContext(ctx, "CALL DOLT_BRANCH('-D', 'compact-tmp')")
		}
	}()

	execSQL := func(name, query string, args ...interface{}) error {
		if _, err := conn.ExecContext(ctx, query, args...); err != nil {
			return fmt.Errorf("compact step %q: %w", name, err)
		}
		return nil
	}

	if err := execSQL("create temp branch", "CALL DOLT_BRANCH('compact-tmp', ?)", boundaryHash); err != nil {
		return err
	}
	branchCreated = true

	if err := execSQL("checkout temp", "CALL DOLT_CHECKOUT('compact-tmp')"); err != nil {
		return err
	}
	if err := execSQL("soft reset to initial", "CALL DOLT_RESET('--soft', ?)", initialHash); err != nil {
		return err
	}
	msg := fmt.Sprintf("compact: squash %d commits into base snapshot", oldCommits)
	if err := execSQL("commit squashed base", "CALL DOLT_COMMIT('-Am', ?)", msg); err != nil {
		return err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the quoted step name in the error and address that specific step (e.g. 'create temp branch' → leftover compact-tmp; 'cherry-pick <hash>' → conflict)
  2. Delete any leftover 'compact-tmp' branch and checkout main before retrying Compact
  3. Verify initialHash/boundaryHash/recentHashes are valid commit hashes (dolt log)
  4. Run Compact on a single dedicated connection (not pooled *sql.DB) since steps depend on session state
  5. Inspect the wrapped driver error for conflict details and resolve before cherry-picks

Example fix

// before
_, err := db.ExecContext(ctx, "CALL DOLT_BRANCH('-D', 'compact-tmp')") // only on failure path
err := versioncontrolops.Compact(ctx, conn, init, boundary, n, hashes)
// after
// idempotent pre-cleanup before retrying
_, _ = conn.ExecContext(ctx, "CALL DOLT_CHECKOUT('main')")
_, _ = conn.ExecContext(ctx, "CALL DOLT_BRANCH('-D', 'compact-tmp')")
err := versioncontrolops.Compact(ctx, conn, init, boundary, n, hashes)
Defensive patterns

Strategy: try-catch

Validate before calling

var tmp int
_ = conn.QueryRowContext(ctx,
    "SELECT COUNT(*) FROM dolt_branches WHERE name = 'compact-tmp'").Scan(&tmp)
if tmp > 0 {
    _, _ = conn.ExecContext(ctx, "CALL DOLT_CHECKOUT('main')")
    _, _ = conn.ExecContext(ctx, "CALL DOLT_BRANCH('-D', 'compact-tmp')")
}
// also verify boundaryHash exists in dolt_log before Compact

Try / catch

err := versioncontrolops.Compact(ctx, conn, init, boundary, n, hashes)
if err != nil {
    var stepName string
    if m := regexp.MustCompile(`compact step "([^"]+)"`).FindStringSubmatch(err.Error()); m != nil {
        stepName = m[1] // route to step-specific recovery (e.g. recreate temp branch, resolve cherry-pick conflict)
    }
    cleanupTempBranch(ctx, conn)
    return fmt.Errorf("compact failed at step %q: %w", stepName, err)
}

Prevention

When it happens

Trigger: Calling versioncontrolops.Compact when any step fails: creating 'compact-tmp' when it already exists (leftover from a prior crash), cherry-picking a commit that conflicts or is empty without --allow-empty, DOLT_RESET on a bad hash, or connection loss mid-recipe.

Common situations: A previous failed Compact left the compact-tmp branch behind; cherry-picking recent commits whose changes conflict with the squashed base; corrupt/invalid initialHash or boundaryHash; session contention because Compact requires a dedicated single connection, not a pool.

Related errors


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