{"record":{"id":"deb94c74db7c3562","repo":"gastownhall/beads","slug":"merged-values-for-issue-v-matched-no-row-was-it","errorCode":null,"errorMessage":"merged values for issue %v matched no row (was it deleted concurrently?); conflict left unresolved","messagePattern":"merged values for issue (.+?) matched no row \\(was it deleted concurrently\\?\\); conflict left unresolved","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/automerge.go","lineNumber":617,"sourceCode":"\t\t\tstmt := fmt.Sprintf(\"UPDATE `issues` SET %s WHERE `%s` = ?\", strings.Join(sets, \", \"), issuesKeyColumn) //nolint:gosec // identifiers validated above\n\t\t\tres, err := db.ExecContext(ctx, stmt, args...)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"apply merged values for issue %v: %w\", m.ourKey, err)\n\t\t\t}\n\t\t\t// Zero rows would mean the row we planned against is gone —\n\t\t\t// another session deleted it between the read and the write, and\n\t\t\t// clearing the conflict now would discard their side undetectably.\n\t\t\t// But RowsAffected is rows CHANGED, not rows MATCHED: the DSN does\n\t\t\t// not set clientFoundRows (doltutil/dsn.go), so a write the backend\n\t\t\t// normalizes to the bytes already stored also reports zero. Only a\n\t\t\t// follow-up existence check can tell \"vanished\" from \"no-op\".\n\t\t\tif n, err := res.RowsAffected(); err != nil || n == 0 {\n\t\t\t\tpresent, err := conflictTargetStillPresent(ctx, db, \"issues\", issuesKeyColumn, m.ourKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"confirm issue %v still exists after writing merged values: %w\", m.ourKey, err)\n\t\t\t\t}\n\t\t\t\tif !present {\n\t\t\t\t\treturn fmt.Errorf(\"merged values for issue %v matched no row (was it deleted concurrently?); conflict left unresolved\", m.ourKey)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tres, err := db.ExecContext(ctx,\n\t\t\t\"DELETE FROM dolt_conflicts_issues WHERE our_\"+issuesKeyColumn+\" = ?\", m.ourKey)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"clear conflict for issue %v: %w\", m.ourKey, err)\n\t\t}\n\t\tif n, err := res.RowsAffected(); err == nil && n == 0 {\n\t\t\treturn fmt.Errorf(\"conflict for issue %v was not cleared (no conflict row deleted)\", m.ourKey)\n\t\t}\n\t}\n\treturn nil\n}\n\n// unionConflictsAreSafe reports whether every live conflict of a union-merged\n// table (labels, comments, events) is the same row on both sides with matching\n// columns — the only class where \"union\" has an unambiguous answer. A row","sourceCodeStart":599,"sourceCodeEnd":635,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/automerge.go#L599-L635","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: auto-merge proceeds even though a concurrent delete may have landed\nerr := TryAutoResolveMergeConflicts(ctx, db)\n// after: detect concurrent-delete errors and fall back to a re-sync\nif err := TryAutoResolveMergeConflicts(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"deleted concurrently\") {\n        // re-pull to pick up the deletion, then re-attempt\n        return pullAndRetry(ctx, db)\n    }\n    return err\n}","handlingStrategy":"validation","validationCode":"// verify the issue still exists immediately before merging\nvar exists int\nerr := db.QueryRowContext(ctx,\n    \"SELECT COUNT(*) FROM `issues` WHERE `id` = ?\", issueID).Scan(&exists)\nif err != nil || exists == 0 {\n    return fmt.Errorf(\"issue %v vanished before merge; re-run pull\", issueID)\n}","typeGuard":"func isConcurrentDelete(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"deleted concurrently\")\n}","tryCatchPattern":"if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {\n    if isConcurrentDelete(err) {\n        // fall back to a fresh pull that will merge/pick up the deletion\n        return pullAndRetryMerge(ctx, db)\n    }\n    return err\n}","preventionTips":["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"],"tags":["database","dolt","merge-conflict","race-condition","concurrent-deletion"],"backgroundTag":"row-deleted-during-merge","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}