{"record":{"id":"8e886fe49ce98cf9","repo":"gastownhall/beads","slug":"confirm-s-s-still-exists-after-writing-their-val","errorCode":null,"errorMessage":"confirm %s %s still exists after writing their values: %w","messagePattern":"confirm (.+?) (.+?) still exists after writing their values: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/conflicts.go","lineNumber":421,"sourceCode":"\t\t\targs = append(args, vals[i])\n\t\t}\n\t\targs = append(args, ourKey)\n\t\tstmt := fmt.Sprintf(\"UPDATE `%s` SET %s WHERE `%s` = ?\", table, strings.Join(sets, \", \"), keyCol) //nolint:gosec // identifiers validated above\n\t\tres, err := db.ExecContext(ctx, stmt, args...)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"apply their values for %s %s: %w\", table, key, err)\n\t\t}\n\t\t// Zero rows would mean the row we read the conflict for is no longer\n\t\t// there — another session on the same branch deleted it between the\n\t\t// read and the write. Clearing the conflict now would discard their\n\t\t// side under a --theirs invocation, undetectably. But zero is not\n\t\t// proof of that on its own (see conflictTargetStillPresent), so ask\n\t\t// before refusing: an operator who named this row deserves the abort\n\t\t// only when the row really is gone.\n\t\tif n, err := res.RowsAffected(); err != nil || n == 0 {\n\t\t\tpresent, err := conflictTargetStillPresent(ctx, db, table, keyCol, ourKey)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"confirm %s %s still exists after writing their values: %w\", table, key, err)\n\t\t\t}\n\t\t\tif !present {\n\t\t\t\treturn fmt.Errorf(\"their values for %s %s matched no row (was it deleted concurrently?); conflict left unresolved\", table, key)\n\t\t\t}\n\t\t}\n\t}\n\n\tdel := fmt.Sprintf(\"DELETE FROM `dolt_conflicts_%s` WHERE `our_%s` = ?\", table, keyCol) //nolint:gosec // identifiers validated\n\tres, err := db.ExecContext(ctx, del, ourKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"clear conflict for %s %s: %w\", table, key, err)\n\t}\n\tif n, err := res.RowsAffected(); err == nil && n == 0 {\n\t\treturn fmt.Errorf(\"conflict for %s %s was not cleared (no conflict row deleted)\", table, key)\n\t}\n\treturn nil\n}\n","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/conflicts.go#L403-L439","documentation":"After the 'theirs' UPDATE reported zero affected rows (or RowsAffected errored), the library runs conflictTargetStillPresent to double-check the target row still exists; that verification query itself failed. This is a safety gate: the library will not clear the conflict while it cannot confirm the row survived the write, so resolution aborts with the wrapped cause.","triggerScenarios":"ResolveConflictRows -> resolveOneConflictRow (theirs): res.RowsAffected() returned 0 or errored, then the `SELECT COUNT(*) FROM <table> WHERE <keyCol> = ?` check fails — connection drop, context cancellation, permission error, or the table became unreadable between the UPDATE and the check.","commonSituations":"Context deadline expiring between the UPDATE and the verification SELECT; server restart or network failure mid-resolution; permissions restricted so COUNT(*) on the base table fails; embedded (autocommit) mode where another writer dropped the table or altered the key column concurrently.","solutions":["Retry the resolution on a fresh connection with a longer context timeout","Check permissions: the caller must be able to SELECT from the base table, not just dolt_conflicts_<table>","Verify the table and key column still exist and were not altered concurrently (SHOW CREATE TABLE)","Resolve the conflict manually in a dolt sql shell if verification keeps failing"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 2*time.Second)\ndefer cancel() // expires during verification\nresolveOne(ctx, db, \"issues\", \"id\", \"42\", \"theirs\")\n// after\nctx, cancel := context.WithTimeout(ctx, 30*time.Second)\ndefer cancel()\nresolveOne(ctx, db, \"issues\", \"id\", \"42\", \"theirs\")","handlingStrategy":"retry","validationCode":"// verify read access to the base table before starting resolution\nif err := db.QueryRowContext(ctx, \"SELECT COUNT(*) FROM `\"+table+\"`\").Scan(&n); err != nil {\n    return fmt.Errorf(\"cannot verify rows of %s: %w\", table, err)\n}","typeGuard":null,"tryCatchPattern":"err := resolveOne(ctx, db, table, keyCol, key, \"theirs\")\nif err != nil && strings.Contains(err.Error(), \"still exists after writing their values\") {\n    // write likely succeeded; verification failed transiently\n    return retryResolveWithFreshContext(ctx, db, table, keyCol, key)\n}\nreturn err","preventionTips":["Grant SELECT on base tables to the resolution identity, not only dolt_conflicts_*","Use long-enough context timeouts to cover UPDATE plus verification","Avoid cancelling resolution mid-flight; let it complete or roll back explicitly","Prefer server mode with transactions over embedded autocommit for multi-step resolution"],"tags":["database","dolt","verification-failed","conflict-resolution"],"backgroundTag":"verification-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}