{"record":{"id":"ed47591fa0697743","repo":"gastownhall/beads","slug":"conflict-for-s-s-carries-no-their-data-columns","errorCode":null,"errorMessage":"conflict for %s %s carries no their_* data columns","messagePattern":"conflict for (.+?) (.+?) carries no their_\\* data columns","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/conflicts.go","lineNumber":390,"sourceCode":"// matters, since the delete is what tells dolt the row is settled.\nfunc resolveOneConflictRow(ctx context.Context, db DBConn, table, keyCol, key, strategy string, row rawConflictRow) error {\n\tourKey, ourOK := row.value(\"our\", keyCol)\n\ttheirKey, theirOK := row.value(\"their\", keyCol)\n\tif !ourOK || !theirOK {\n\t\treturn fmt.Errorf(\"conflict table dolt_conflicts_%s has no our_%s/their_%s column\", table, keyCol, keyCol)\n\t}\n\tif ourKey == nil || theirKey == nil {\n\t\t// delete/modify (one side removed the row) or add/add against a\n\t\t// missing key: refuse by name. Row-level ours/theirs would silently\n\t\t// resurrect or destroy a row the operator never looked at.\n\t\treturn fmt.Errorf(\"conflict for %s %s is not a modify/modify conflict (one side has no row); \"+\n\t\t\t\"resolve it with a whole-table strategy or edit the row directly\", table, key)\n\t}\n\n\tif strategy == ConflictStrategyTheirs {\n\t\tnames, vals := row.theirFields(keyCol)\n\t\tif len(names) == 0 {\n\t\t\treturn fmt.Errorf(\"conflict for %s %s carries no their_* data columns\", table, key)\n\t\t}\n\t\tsets := make([]string, len(names))\n\t\targs := make([]any, 0, len(names)+1)\n\t\tfor i, n := range names {\n\t\t\t// Column names are interpolated (MySQL cannot bind an\n\t\t\t// identifier) and come from the conflict table's own schema,\n\t\t\t// which a peer's schema merge can extend — gate them exactly\n\t\t\t// like the table name rather than trusting the source.\n\t\t\tif err := ValidateConflictTable(n); err != nil {\n\t\t\t\treturn fmt.Errorf(\"refusing to write unexpected column %q of %s: %w\", n, table, err)\n\t\t\t}\n\t\t\tsets[i] = fmt.Sprintf(\"`%s` = ?\", n)\n\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 {","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/conflicts.go#L372-L408","documentation":"When applying the 'theirs' strategy, resolveOneConflictRow collects their_* data columns from the conflict row to write back into the base table; none were found. This means the conflict row carries no transferable their-side data beyond metadata and the key, so there is nothing to apply. Usually indicates an unexpected conflict-table shape or an empty their-side payload.","triggerScenarios":"ResolveConflictRows -> resolveOneConflictRow with strategy == theirs calls row.theirFields(keyCol), which filters out our_*, metadata-suffixed columns and the key column; the result is empty, e.g. when the conflict table has only key/metadata columns or column naming deviates from the base_<col>/their_<col> convention.","commonSituations":"Dolt version whose conflict-table metadata columns are not in the expected suffix set; conflicts created against a table whose data columns were all dropped in a schema merge; manually constructed conflict rows.","solutions":["Inspect `SELECT * FROM dolt_conflicts_<table>` to see what their_* columns actually exist","Check the dolt version; align peers on a version whose conflict-table layout matches this library's expectations","Fall back to whole-table --theirs resolution, which rebuilds rows from the branch rather than the conflict row","Abort and redo the merge if the conflict table looks structurally corrupted"],"exampleFix":"null","handlingStrategy":"validation","validationCode":"// verify the conflict row carries their_* data columns before theirs strategy\ncols, _ := conflictTableColumns(ctx, db, table)\nhasData := false\nfor _, c := range cols {\n    if strings.HasPrefix(c, \"their_\") && !isMetaOrKey(c, keyCol) { hasData = true }\n}\nif !hasData && strategy == \"theirs\" { return resolveWholeTable(ctx, db, table, \"theirs\") }","typeGuard":null,"tryCatchPattern":"err := resolveOne(ctx, db, table, keyCol, key, \"theirs\")\nif err != nil && strings.Contains(err.Error(), \"carries no their_* data columns\") {\n    return resolveWholeTable(ctx, db, table, \"theirs\")\n}\nreturn err","preventionTips":["Keep dolt versions aligned so conflict-table layouts match expectations","Avoid dropping all data columns via schema merges on conflicted tables","Inspect dolt_conflicts_<table> when unsure of its shape","Default to whole-table strategy for structurally unusual conflicts"],"tags":["database","dolt","conflict-resolution","schema"],"backgroundTag":"conflict-schema-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}