gastownhall/beads · error
refusing to write unexpected column %q of %s: %w
Error message
refusing to write unexpected column %q of %s: %w
What it means
Before building the UPDATE for a 'theirs' resolution, every their_* column name is passed through ValidateConflictTable because identifiers cannot be bound as SQL parameters and a peer's schema merge could have introduced hostile or unexpected column names. When a column name fails validation, the library refuses to interpolate it into the UPDATE, wrapping the validation error.
Source
Thrown at internal/storage/versioncontrolops/conflicts.go:400
// resurrect or destroy a row the operator never looked at.
return fmt.Errorf("conflict for %s %s is not a modify/modify conflict (one side has no row); "+
"resolve it with a whole-table strategy or edit the row directly", table, key)
}
if strategy == ConflictStrategyTheirs {
names, vals := row.theirFields(keyCol)
if len(names) == 0 {
return fmt.Errorf("conflict for %s %s carries no their_* data columns", table, key)
}
sets := make([]string, len(names))
args := make([]any, 0, len(names)+1)
for i, n := range names {
// Column names are interpolated (MySQL cannot bind an
// identifier) and come from the conflict table's own schema,
// which a peer's schema merge can extend — gate them exactly
// like the table name rather than trusting the source.
if err := ValidateConflictTable(n); err != nil {
return fmt.Errorf("refusing to write unexpected column %q of %s: %w", n, table, err)
}
sets[i] = fmt.Sprintf("`%s` = ?", n)
args = append(args, vals[i])
}
args = append(args, ourKey)
stmt := fmt.Sprintf("UPDATE `%s` SET %s WHERE `%s` = ?", table, strings.Join(sets, ", "), keyCol) //nolint:gosec // identifiers validated above
res, err := db.ExecContext(ctx, stmt, args...)
if err != nil {
return fmt.Errorf("apply their values for %s %s: %w", table, key, err)
}
// Zero rows would mean the row we read the conflict for is no longer
// there — another session on the same branch deleted it between the
// read and the write. Clearing the conflict now would discard their
// side under a --theirs invocation, undetectably. But zero is not
// proof of that on its own (see conflictTargetStillPresent), so ask
// before refusing: an operator who named this row deserves the abort
// only when the row really is gone.
if n, err := res.RowsAffected(); err != nil || n == 0 {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the peer's schema and rename/remove the offending column before merging
- Abort the merge, fix the schema on the offending branch, and redo the merge
- Drop and recreate the conflict via whole-table resolution after the schema is corrected
- Only merge from trusted remotes; review schema changes before pulling
Example fix
// before -- peer branch schema col `user name` VARCHAR(...) -- fails ValidateConflictTable // after ALTER TABLE t RENAME COLUMN `user name` TO user_name;
Defensive patterns
Strategy: validation
Validate before calling
// pre-validate all their_* column names against the same rule the library uses
for _, c := range conflictColumns {
if err := ValidateConflictTable(strings.TrimPrefix(c, "their_")); err != nil {
return fmt.Errorf("peer schema has invalid column %q: %w", c, err)
}
} Try / catch
err := resolveOne(ctx, db, table, keyCol, key, "theirs")
var ve *ValidationError
if err != nil && errors.As(err, &ve) && strings.Contains(err.Error(), "refusing to write unexpected column") {
return rejectPeerSchema(ctx, table, ve)
}
return err Prevention
- Only merge from trusted remotes
- Review schema diffs before pulling peer changes
- Enforce naming conventions (snake_case identifiers) repo-wide
- Validate identifiers at schema-migration time, not merge time
When it happens
Trigger: ResolveConflictRows -> resolveOneConflictRow (theirs strategy) iterates row.theirFields(keyCol); a their_* column name contains characters disallowed by ValidateConflictTable (quotes, backticks, spaces, injection-shaped names) introduced via a peer's schema merge or a corrupted conflict table.
Common situations: Merging with a peer whose schema was modified to include odd column names; a malicious/buggy remote pushing a crafted schema; schema drift where conflict-table columns no longer match the base table's validated naming rules.
Related errors
- no Dolt remote is configured, and bd will not adopt one with
- dolt: credential from %s is not an identity; refusing to pre
- invalid ref format: %s
- unsafe dolt status table name %q
- multiple .doltcfg directories detected
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/05ea4b2b6f849252.
Report an issue: GitHub.