gastownhall/beads · error
merged values for issue %v matched no row (was it deleted co
Error message
merged values for issue %v matched no row (was it deleted concurrently?); conflict left unresolved
What it means
The merged-values UPDATE matched zero rows and the follow-up existence check confirmed the issue row is gone: another session deleted the issue between the conflict scan and the write. Clearing the conflict in that state would silently discard the deleted side's change, so the code refuses and leaves the conflict unresolved with this error.
Source
Thrown at internal/storage/versioncontrolops/automerge.go:617
stmt := fmt.Sprintf("UPDATE `issues` SET %s WHERE `%s` = ?", strings.Join(sets, ", "), issuesKeyColumn) //nolint:gosec // identifiers validated above
res, err := db.ExecContext(ctx, stmt, args...)
if err != nil {
return fmt.Errorf("apply merged values for issue %v: %w", m.ourKey, err)
}
// Zero rows would mean the row we planned against is gone —
// another session deleted it between the read and the write, and
// clearing the conflict now would discard their side undetectably.
// But RowsAffected is rows CHANGED, not rows MATCHED: the DSN does
// not set clientFoundRows (doltutil/dsn.go), so a write the backend
// normalizes to the bytes already stored also reports zero. Only a
// follow-up existence check can tell "vanished" from "no-op".
if n, err := res.RowsAffected(); err != nil || n == 0 {
present, err := conflictTargetStillPresent(ctx, db, "issues", issuesKeyColumn, m.ourKey)
if err != nil {
return fmt.Errorf("confirm issue %v still exists after writing merged values: %w", m.ourKey, err)
}
if !present {
return fmt.Errorf("merged values for issue %v matched no row (was it deleted concurrently?); conflict left unresolved", m.ourKey)
}
}
}
res, err := db.ExecContext(ctx,
"DELETE FROM dolt_conflicts_issues WHERE our_"+issuesKeyColumn+" = ?", m.ourKey)
if err != nil {
return fmt.Errorf("clear conflict for issue %v: %w", m.ourKey, err)
}
if n, err := res.RowsAffected(); err == nil && n == 0 {
return fmt.Errorf("conflict for issue %v was not cleared (no conflict row deleted)", m.ourKey)
}
}
return nil
}
// unionConflictsAreSafe reports whether every live conflict of a union-merged
// table (labels, comments, events) is the same row on both sides with matching
// columns — the only class where "union" has an unambiguous answer. A rowView on GitHub (pinned to 71377f2769)
Solutions
- Re-run `bd dolt pull` / merge: the deletion will now conflict or merge normally, and the stale plan is discarded.
- Check whether the deletion was intended; if not, restore the issue from the other branch/parent (`bd dolt` parent queries or git-style history) and retry.
- Serialize concurrent sessions — ensure only one `bd` process merges at a time (e.g. take a lock or re-run after the other finishes).
- If the deletion is correct, resolve the conflict manually toward the deleted side (delete our row / DOLT_CONFLICTS_RESOLVE) instead of relying on auto-merge.
Example fix
// before: auto-merge proceeds even though a concurrent delete may have landed
err := TryAutoResolveMergeConflicts(ctx, db)
// after: detect concurrent-delete errors and fall back to a re-sync
if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {
if strings.Contains(err.Error(), "deleted concurrently") {
// re-pull to pick up the deletion, then re-attempt
return pullAndRetry(ctx, db)
}
return err
} Defensive patterns
Strategy: validation
Validate before calling
// verify the issue still exists immediately before merging
var exists int
err := db.QueryRowContext(ctx,
"SELECT COUNT(*) FROM `issues` WHERE `id` = ?", issueID).Scan(&exists)
if err != nil || exists == 0 {
return fmt.Errorf("issue %v vanished before merge; re-run pull", issueID)
} Type guard
func isConcurrentDelete(err error) bool {
return err != nil && strings.Contains(err.Error(), "deleted concurrently")
} Try / catch
if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {
if isConcurrentDelete(err) {
// fall back to a fresh pull that will merge/pick up the deletion
return pullAndRetryMerge(ctx, db)
}
return err
} Prevention
- Serialize `bd` sessions that mutate issues (deletes/closes) with sessions that merge
- Avoid running purge/GC scripts while a pull or merge is in flight
- Re-pull immediately before resolving conflicts if other agents may be active
- Treat 'deleted concurrently' as a signal to re-sync rather than force-resolving
When it happens
Trigger: Between issuesConflictsAreFieldMergeable reading dolt_conflicts_issues and the UPDATE in resolveIssuesFieldMerge, a concurrent session (or a rebase/purge command) deleted the issue identified by m.ourKey from the working set, so `UPDATE issues ... WHERE <key> = ?` matched nothing and conflictTargetStillPresent returns false.
Common situations: Two operators or an automation job merge/pull at the same time; one closed or deleted the issue while the other merged field changes for it; a cleanup script (bd gc / purge of closed issues) ran during a pull; CI merging a branch whose issues were deleted on the base branch.
Related errors
- conflict for issue %v was not cleared (no conflict row delet
- schema: verify fresh-bootstrap history: got %d commits, want
- schema: release migration lock: %w: returned %d
- apply merged values for issue %v: %w
- confirm issue %v still exists after writing merged values: %
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/deb94c74db7c3562.
Report an issue: GitHub.