gastownhall/beads · error
confirm %s %s still exists after writing their values: %w
Error message
confirm %s %s still exists after writing their values: %w
What it means
After the 'theirs' UPDATE reported zero affected rows (or RowsAffected errored), the library runs conflictTargetStillPresent to double-check the target row still exists; that verification query itself failed. This is a safety gate: the library will not clear the conflict while it cannot confirm the row survived the write, so resolution aborts with the wrapped cause.
Source
Thrown at internal/storage/versioncontrolops/conflicts.go:421
args = append(args, vals[i])
}
args = append(args, ourKey)
stmt := fmt.Sprintf("UPDATE `%s` SET %s WHERE `%s` = ?", table, strings.Join(sets, ", "), keyCol) //nolint:gosec // identifiers validated above
res, err := db.ExecContext(ctx, stmt, args...)
if err != nil {
return fmt.Errorf("apply their values for %s %s: %w", table, key, err)
}
// Zero rows would mean the row we read the conflict for is no longer
// there — another session on the same branch deleted it between the
// read and the write. Clearing the conflict now would discard their
// side under a --theirs invocation, undetectably. But zero is not
// proof of that on its own (see conflictTargetStillPresent), so ask
// before refusing: an operator who named this row deserves the abort
// only when the row really is gone.
if n, err := res.RowsAffected(); err != nil || n == 0 {
present, err := conflictTargetStillPresent(ctx, db, table, keyCol, ourKey)
if err != nil {
return fmt.Errorf("confirm %s %s still exists after writing their values: %w", table, key, err)
}
if !present {
return fmt.Errorf("their values for %s %s matched no row (was it deleted concurrently?); conflict left unresolved", table, key)
}
}
}
del := fmt.Sprintf("DELETE FROM `dolt_conflicts_%s` WHERE `our_%s` = ?", table, keyCol) //nolint:gosec // identifiers validated
res, err := db.ExecContext(ctx, del, ourKey)
if err != nil {
return fmt.Errorf("clear conflict for %s %s: %w", table, key, err)
}
if n, err := res.RowsAffected(); err == nil && n == 0 {
return fmt.Errorf("conflict for %s %s was not cleared (no conflict row deleted)", table, key)
}
return nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Retry the resolution on a fresh connection with a longer context timeout
- Check permissions: the caller must be able to SELECT from the base table, not just dolt_conflicts_<table>
- Verify the table and key column still exist and were not altered concurrently (SHOW CREATE TABLE)
- Resolve the conflict manually in a dolt sql shell if verification keeps failing
Example fix
// before ctx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel() // expires during verification resolveOne(ctx, db, "issues", "id", "42", "theirs") // after ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() resolveOne(ctx, db, "issues", "id", "42", "theirs")
Defensive patterns
Strategy: retry
Validate before calling
// verify read access to the base table before starting resolution
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM `"+table+"`").Scan(&n); err != nil {
return fmt.Errorf("cannot verify rows of %s: %w", table, err)
} Try / catch
err := resolveOne(ctx, db, table, keyCol, key, "theirs")
if err != nil && strings.Contains(err.Error(), "still exists after writing their values") {
// write likely succeeded; verification failed transiently
return retryResolveWithFreshContext(ctx, db, table, keyCol, key)
}
return err Prevention
- Grant SELECT on base tables to the resolution identity, not only dolt_conflicts_*
- Use long-enough context timeouts to cover UPDATE plus verification
- Avoid cancelling resolution mid-flight; let it complete or roll back explicitly
- Prefer server mode with transactions over embedded autocommit for multi-step resolution
When it happens
Trigger: ResolveConflictRows -> resolveOneConflictRow (theirs): res.RowsAffected() returned 0 or errored, then the `SELECT COUNT(*) FROM <table> WHERE <keyCol> = ?` check fails — connection drop, context cancellation, permission error, or the table became unreadable between the UPDATE and the check.
Common situations: Context deadline expiring between the UPDATE and the verification SELECT; server restart or network failure mid-resolution; permissions restricted so COUNT(*) on the base table fails; embedded (autocommit) mode where another writer dropped the table or altered the key column concurrently.
Related errors
- query conflicts for table %s: %w
- query conflict for %s %s: %w
- no live conflict for %s %s
- multiple conflict rows for %s %s; resolve the whole table in
- conflict table dolt_conflicts_%s has no our_%s/their_%s colu
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8e886fe49ce98cf9.
Report an issue: GitHub.