{"record":{"id":"2c37af6bdd20f7c5","repo":"gastownhall/beads","slug":"conflict-for-issue-v-was-not-cleared-no-conflict","errorCode":null,"errorMessage":"conflict for issue %v was not cleared (no conflict row deleted)","messagePattern":"conflict for issue (.+?) was not cleared \\(no conflict row deleted\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/storage/versioncontrolops/automerge.go","lineNumber":627,"sourceCode":"\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\n// missing on one side (a deletion racing an insert) or diverging columns in a\n// supposedly immutable row goes to the operator.\nfunc unionConflictsAreSafe(ctx context.Context, db DBConn, table string) ([]unionRowKey, bool, error) {\n\tkeyCols, ok := unionConflictKeyColumns[table]\n\tif !ok {\n\t\treturn nil, false, fmt.Errorf(\"table %s is not union-mergeable\", table)\n\t}\n\trows, err := loadConflictRows(ctx, db, table)\n\tif err != nil {\n\t\treturn nil, false, err","sourceCodeStart":609,"sourceCodeEnd":645,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/automerge.go#L609-L645","documentation":"After the UPDATE succeeded, the DELETE that clears dolt_conflicts_issues reported zero rows deleted (and no driver error). That means the conflict row this plan entry was built from no longer exists — typically because a concurrent process already resolved or removed it — so this code refuses to report success and aborts, since the plan is now stale.","triggerScenarios":"Between loadConflictRows and the DELETE, another session cleared the conflict (ran its own auto-resolve or DOLT_CONFLICTS_RESOLVE), or Dolt collapsed the conflict row after the working-set UPDATE made the sides identical; the plan's key no longer matches any row in dolt_conflicts_issues.","commonSituations":"Two `bd` sessions auto-resolving the same pull simultaneously; a scheduled sync job racing an interactive merge; dolt version where conflict rows are auto-pruned once cells match; user manually resolving conflicts in a SQL shell while an agent runs auto-merge.","solutions":["Re-run the merge: reload the conflict list fresh so the plan matches current dolt_conflicts_issues state.","Check for concurrent `bd`/agent sessions on the same database and serialize them (only one resolver at a time).","Verify how the conflict actually got cleared (parent queries / dolt history) — if another resolver settled it correctly, the re-run should simply find no conflicts.","If it recurs with no visible competitor, check for hooks or cron jobs running `bd` sync concurrently."],"exampleFix":"// before: two processes can both auto-resolve the same merge\nTryAutoResolveMergeConflicts(ctx, db)\n// after: take an advisory lock / single-flight guard before resolving\nif err := acquireMergeLock(ctx, db); err != nil {\n    return fmt.Errorf(\"another resolver holds the merge lock: %w\", err)\n}\ndefer releaseMergeLock(ctx, db)\nreturn TryAutoResolveMergeConflicts(ctx, db)","handlingStrategy":"retry","validationCode":"// snapshot conflict keys, resolve, and confirm state did not move underneath\nrows, err := loadConflictKeys(ctx, db, \"issues\")\nif err != nil {\n    return err\n}\nif len(rows) == 0 {\n    return nil // nothing to resolve — a competitor already cleared them\n}","typeGuard":"func isStaleConflictPlan(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"no conflict row deleted\")\n}","tryCatchPattern":"if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {\n    if isStaleConflictPlan(err) {\n        // someone else resolved it; reload state and retry — second pass finds nothing\n        return TryAutoResolveMergeConflicts(ctx, db)\n    }\n    return err\n}","preventionTips":["Take a merge/resolution lock so only one process auto-resolves at a time","Do not manually run DOLT_CONFLICTS_RESOLVE while an agent is auto-merging","Check for cron/CI jobs running `bd` sync on the same database concurrently","Treat zero-deleted as 'someone else settled it' and re-check state rather than force-clearing"],"tags":["database","dolt","merge-conflict","race-condition","stale-plan"],"backgroundTag":"conflict-already-resolved","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}