{"record":{"id":"793d2a38d92f6a63","repo":"gastownhall/beads","slug":"confirm-issue-v-still-exists-after-writing-merged","errorCode":null,"errorMessage":"confirm issue %v still exists after writing merged values: %w","messagePattern":"confirm issue (.+?) still exists after writing merged values: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/automerge.go","lineNumber":614,"sourceCode":"\t\t\t\targs = append(args, m.values[i])\n\t\t\t}\n\t\t\targs = append(args, m.ourKey)\n\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","sourceCodeStart":596,"sourceCodeEnd":632,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/automerge.go#L596-L632","documentation":"After the merged-values UPDATE reports zero RowsAffected (or RowsAffected itself errors), the code must distinguish 'row already had these values' from 'row vanished'. It re-queries via conflictTargetStillPresent; if that existence check itself fails, this error wraps the underlying query error. The conflict stays unresolved and the auto-merge aborts to avoid discarding a side undetectably.","triggerScenarios":"RowsAffected returned 0 or errored after the merged-values UPDATE, and the follow-up SELECT in conflictTargetStillPresent(ctx, db, \"issues\", ...) failed — connection drop, the existence query hit a timeout, or the dolt_conflicts_issues/working-set metadata tables are unreadable at that moment.","commonSituations":"Unstable connection to a remote Dolt server (two queries in quick succession, second one drops); server restarting between the UPDATE and the existence check; transient network partition during merge; query cancellation because the caller's ctx deadline expired mid-merge.","solutions":["Inspect the wrapped %w driver error; if it is a timeout/cancellation, raise the context deadline or timeout for the merge command and retry.","Confirm the Dolt server is up and stable (`bd doctor` / connection check), then re-run the merge; the conflict row is still present.","If network is flaky, run the merge locally against the embedded database instead of a remote server, then push.","Ensure the `issues` working-set table and its key column are queryable by the connection user (SELECT privilege)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// pre-flight: ensure the connection survives a quick round-trip\nctx, cancel := context.WithTimeout(ctx, 30*time.Second)\ndefer cancel()\nif err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"database ping failed before merge: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := TryAutoResolveMergeConflicts(ctx, db)\nif err != nil && strings.Contains(err.Error(), \"confirm issue\") {\n    // transient existence-check failure: retry once after backoff\n    time.Sleep(2 * time.Second)\n    err = TryAutoResolveMergeConflicts(ctx, db)\n}\nreturn err","preventionTips":["Use generous context timeouts for merge operations (two sequential queries must both succeed)","Check network stability to remote Dolt servers before long-running merges","Avoid server restarts/maintenance windows during merge operations","Prefer local embedded mode when the remote connection is flaky"],"tags":["database","dolt","merge-conflict","existence-check","transient-error"],"backgroundTag":"transient-db-connection-failure","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}