{"record":{"id":"e51fe45c8306b359","repo":"gastownhall/beads","slug":"failed-to-commit-resolved-conflicts-w","errorCode":null,"errorMessage":"failed to commit resolved conflicts: %w","messagePattern":"failed to commit resolved conflicts: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/mergesettle.go","lineNumber":581,"sourceCode":"\t\t//nolint:gosec // G201: table is one of the hardcoded constants above.\n\t\tif _, err := db.ExecContext(ctx, \"CALL DOLT_ADD('\"+table+\"')\"); err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to stage %s: %w\", table, err)\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// CommitResolvedConflicts creates the dolt commit that concludes a merge whose\n// conflicts TryAutoResolveMergeConflicts settled. Callers that saw\n// resolved=true MUST call this, and only AFTER TryRepairFKCascadeViolations\n// has run: DOLT_COMMIT refuses a working set with outstanding constraint\n// violations, so a merge carrying both an auto-resolvable conflict and an FK\n// cascade violation could never settle while the resolver committed first\n// (bd-578h9.14).\nfunc CommitResolvedConflicts(ctx context.Context, db DBConn) error {\n\tif _, err := db.ExecContext(ctx, \"CALL DOLT_COMMIT('-m', 'auto-resolve merge conflicts: metadata, dependencies, schema_migrations, config, issues (field-level three-way merge), labels/comments/events (union)')\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit resolved conflicts: %w\", err)\n\t}\n\treturn nil\n}\n\n// dependencyConflictsAreAuditOnly reports whether every conflicted row in the\n// dependencies table is the SAME logical edge on both sides that differs only in\n// audit columns (created_at/created_by/metadata/thread_id) — the only class safe to\n// auto-resolve with --theirs.\n//\n// It does NOT trust the primary key as proof of a shared edge. With deterministic\n// ids the same edge has the same id on every clone, but an issue rename can leave a\n// row's surrogate id stale (depid.New(oldID, target)) while issue_id/target have\n// already moved (#4259 finding 2), so two genuinely different edges could collide on\n// one id. We therefore verify the natural identity — issue_id and the resolved\n// target — matches on both sides, and that the type matches, before declaring the\n// conflict audit-only. It returns false if any conflicted row differs in identity or\n// type, or was deleted on one side (an add/delete conflict).\nfunc dependencyConflictsAreAuditOnly(ctx context.Context, db DBConn) (bool, error) {","sourceCodeStart":563,"sourceCodeEnd":599,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/mergesettle.go#L563-L599","documentation":"CommitResolvedConflicts runs CALL DOLT_COMMIT('-m', ...) to commit the working set after all merge conflicts were auto-resolved. DOLT_COMMIT refuses a working set with outstanding constraint violations (e.g. FK cascade violations, per bd-578h9.14), so the commit fails and the merge cannot settle even though conflict resolution itself succeeded.","triggerScenarios":"SettleMerge → CommitResolvedConflicts calls DOLT_COMMIT while the resolved working set still violates constraints — most commonly foreign-key cascade violations in tables that were force-resolved with '--theirs', or unresolved conflicts remaining in a table not covered by the auto-resolver.","commonSituations":"A merge carries both an auto-resolvable conflict and an FK violation in another table (the exact bd-578h9.14 scenario); '--theirs' resolution restored rows whose references were deleted on our side; dangling dependency rows referencing issues deleted by the merge.","solutions":["Check dolt_status for remaining conflicts — any un-resolved table blocks DOLT_COMMIT; resolve or abort them first.","Inspect constraint/FK violations in the resolved tables; delete or repair dangling rows (e.g. orphaned dependencies) before committing.","If the merge cannot be settled automatically, abort (CALL DOLT_MERGE('--abort')) and re-merge after fixing the source data.","Ensure CommitResolvedConflicts runs only after ALL resolvers finished, including constraint-violation cleanup, not just conflict resolution."],"exampleFix":"// before\nfunc CommitResolvedConflicts(ctx context.Context, db DBConn) error {\n\tif _, err := db.ExecContext(ctx, \"CALL DOLT_COMMIT('-m', 'auto-resolve merge conflicts: ...')\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit resolved conflicts: %w\", err)\n\t}\n\treturn nil\n}\n// after\nfunc CommitResolvedConflicts(ctx context.Context, db DBConn) error {\n\tvar remaining int\n\tif err := db.QueryRowContext(ctx, \"SELECT COUNT(*) FROM dolt_conflicts\").Scan(&remaining); err == nil && remaining > 0 {\n\t\treturn fmt.Errorf(\"cannot commit: %d tables still have unresolved conflicts\", remaining)\n\t}\n\tif _, err := db.ExecContext(ctx, \"CALL DOLT_COMMIT('-m', 'auto-resolve merge conflicts: ...')\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit resolved conflicts: %w\", err)\n\t}\n\treturn nil\n}","handlingStrategy":"validation","validationCode":"var remainingConflicts, fkViolations int\ndb.QueryRowContext(ctx, \"SELECT COUNT(*) FROM dolt_conflicts\").Scan(&remainingConflicts)\ndb.QueryRowContext(ctx, \"SELECT COUNT(*) FROM dependencies d LEFT JOIN issues i ON d.depends_on_issue_id = i.id WHERE d.depends_on_issue_id IS NOT NULL AND i.id IS NULL\").Scan(&fkViolations)\n// only call CommitResolvedConflicts when both are 0","typeGuard":"func workingSetCommitReady(ctx context.Context, db DBConn) bool {\n\tvar conflicts int\n\tif err := db.QueryRowContext(ctx, \"SELECT COUNT(*) FROM dolt_conflicts\").Scan(&conflicts); err != nil || conflicts > 0 {\n\t\treturn false\n\t}\n\treturn true\n}","tryCatchPattern":"if err := CommitResolvedConflicts(ctx, db); err != nil {\n\t// constraint violation suspected: inspect dangling rows, clean them, retry commit\n\tvar dangling int\n\tdb.QueryRowContext(ctx, \"SELECT COUNT(*) FROM dependencies d LEFT JOIN issues i ON d.depends_on_issue_id = i.id WHERE i.id IS NULL AND d.depends_on_issue_id IS NOT NULL\").Scan(&dangling)\n\tif dangling > 0 {\n\t\tdb.ExecContext(ctx, \"DELETE FROM dependencies WHERE depends_on_issue_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM issues WHERE id = depends_on_issue_id)\")\n\t\terr = CommitResolvedConflicts(ctx, db) // retry once\n\t}\n}","preventionTips":["Run all resolvers (conflicts AND constraint-violation cleanup) before committing — the bd-578h9.14 ordering bug.","Before merging, validate referential integrity so '--theirs' resolution cannot restore dangling rows.","After auto-resolve, always check dolt_conflicts is empty before DOLT_COMMIT.","On failure, abort the merge rather than leaving a half-resolved working set."],"tags":["dolt","commit","foreign-key","merge-conflicts"],"backgroundTag":"dolt-commit-constraint-violation","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}