gastownhall/beads · error
rollback checked close savepoint: %w
Error message
rollback checked close savepoint: %w
What it means
Beads throws this when 'ROLLBACK TO SAVEPOINT <name>' fails after a checked close trial determined the close could not proceed (e.g. open children). Rolling back the savepoint undoes the trial-run writes; failing here leaves the transaction in an indeterminate state, so the caller joins this error with the release error and aborts.
Source
Thrown at internal/storage/issueops/close.go:251
func createCloseCheckedSavepoint(ctx context.Context, tx DBTX) (string, error) {
name := closeCheckedSavepointPrefix + strconv.FormatUint(closeCheckedSavepointCounter.Add(1), 10)
//nolint:gosec // G201: name is a fixed identifier-safe prefix plus an atomic decimal counter.
if _, err := tx.ExecContext(ctx, "SAVEPOINT "+name); err != nil {
return "", fmt.Errorf("create checked close savepoint: %w", err)
}
return name, nil
}
func rollbackAndReleaseCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {
rollbackErr := rollbackToCloseCheckedSavepoint(ctx, tx, name)
releaseErr := releaseCloseCheckedSavepoint(ctx, tx, name)
return errors.Join(rollbackErr, releaseErr)
}
func rollbackToCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {
//nolint:gosec // G201: name is generated by createCloseCheckedSavepoint.
if _, err := tx.ExecContext(ctx, "ROLLBACK TO SAVEPOINT "+name); err != nil {
return fmt.Errorf("rollback checked close savepoint: %w", err)
}
return nil
}
func releaseCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {
//nolint:gosec // G201: name is generated by createCloseCheckedSavepoint.
if _, err := tx.ExecContext(ctx, "RELEASE SAVEPOINT "+name); err != nil {
return fmt.Errorf("release checked close savepoint: %w", err)
}
return nil
}
// dependencyTargetColumnForIDInTx returns the typed target column for id,
// preferring a durable row when an id is present in both tables.
func dependencyTargetColumnForIDInTx(ctx context.Context, tx DBTX, id string) (string, error) {
var found int
err := tx.QueryRowContext(ctx, "SELECT 1 FROM issues WHERE id = ?", id).Scan(&found)
if err == nil {View on GitHub (pinned to 71377f2769)
Solutions
- Check for a preceding error in the same transaction (deadlock/lock timeout) — that original failure is the root cause; fix or retry for it
- Retry the whole close operation; transactional semantics make a full retry safe after a connection-level rollback
- Avoid nesting beads transactions with external savepoint management on the same connection
- If using a custom driver, verify it preserves savepoints after statement-level errors
Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure no prior statement has poisoned the transaction before the trial
if err := tx.QueryRowContext(ctx, "SELECT 1").Scan(&one); err != nil {
return fmt.Errorf("transaction already aborted, savepoint rollback will fail: %w", err)
} Try / catch
if err := CloseIssueCheckedInTx(ctx, tx, id, opts); err != nil {
var joined interface{ Unwrap() []error }
if errors.As(err, &joined) {
for _, sub := range joined.Unwrap() {
if dberrors.IsDeadlock(sub) || dberrors.IsLockTimeout(sub) {
return retryClose(ctx, id) // root cause was the earlier statement
}
}
}
return err
} Prevention
- On deadlock/lock-timeout, retry the entire transaction — the savepoint no longer exists after implicit rollback
- Do not RELEASE the same savepoint twice in your own code around beads calls
- Keep beads' savepoint lifecycle internal; don't interleave manual savepoint SQL on the same tx
- Detect deadlocks early and back off with jitter before retrying
When it happens
Trigger: CloseIssueCheckedInTx (or rollbackAndReleaseCloseCheckedSavepoint) invoking ROLLBACK TO SAVEPOINT when: the savepoint no longer exists (released or outer rollback already occurred), the transaction was already terminated by a prior error (e.g. deadlock or lock timeout), or the connection dropped mid-transaction.
Common situations: A prior statement in the transaction caused an implicit transaction rollback (deadlock, serialization failure), making the savepoint vanish; another layer released the same savepoint; connection pool reclaiming a broken connection.
Related errors
- create checked close savepoint: %w
- release checked close savepoint: %w
- failed to create issue: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e1a0aa2a2e7062dd.
Report an issue: GitHub.