gastownhall/beads · error
refusing to write unexpected column %q of issues: %w
Error message
refusing to write unexpected column %q of issues: %w
What it means
Before building the UPDATE that writes merged values back to `issues`, resolveIssuesFieldMerge validates every column name with ValidateConflictTable (the same identifier gate used for table names), because a peer's schema merge can extend the conflict table's columns with names this code never anticipated. This error wraps that refusal, preventing an unvalidated identifier from being interpolated into SQL.
Source
Thrown at internal/storage/versioncontrolops/automerge.go:593
// side's value was superseded by timestamp. That supersession is
// otherwise undiagnosable once the conflict row is gone — the same
// reason the config path names its resolved keys.
fmt.Fprintf(os.Stderr,
"Notice: auto-merged issue %v; %s settled last-write-wins (the older side's edit was superseded)\n",
m.ourKey, strings.Join(m.lww, ", "))
}
if m.ourKey == nil {
return fmt.Errorf("unexpected conflict row with no issue id (safety check bypassed)")
}
if len(m.columns) > 0 {
sets := make([]string, len(m.columns))
args := make([]any, 0, len(m.columns)+1)
for i, col := range m.columns {
// MySQL cannot bind an identifier and a peer's schema merge can
// extend the conflict table's columns, so gate every name the
// same way the table name is gated.
if err := ValidateConflictTable(col); err != nil {
return fmt.Errorf("refusing to write unexpected column %q of issues: %w", col, err)
}
sets[i] = fmt.Sprintf("`%s` = ?", col)
args = append(args, m.values[i])
}
args = append(args, m.ourKey)
stmt := fmt.Sprintf("UPDATE `issues` SET %s WHERE `%s` = ?", strings.Join(sets, ", "), issuesKeyColumn) //nolint:gosec // identifiers validated above
res, err := db.ExecContext(ctx, stmt, args...)
if err != nil {
return fmt.Errorf("apply merged values for issue %v: %w", m.ourKey, err)
}
// Zero rows would mean the row we planned against is gone —
// another session deleted it between the read and the write, and
// clearing the conflict now would discard their side undetectably.
// But RowsAffected is rows CHANGED, not rows MATCHED: the DSN does
// not set clientFoundRows (doltutil/dsn.go), so a write the backend
// normalizes to the bytes already stored also reports zero. Only a
// follow-up existence check can tell "vanished" from "no-op".
if n, err := res.RowsAffected(); err != nil || n == 0 {View on GitHub (pinned to 71377f2769)
Solutions
- Identify the offending column from the %q in the message
- Normalize the schema across branches (align column names to valid identifiers) and re-merge
- Resolve the affected conflicts manually with DOLT_CONFLICTS_RESOLVE, then update beads/schema to the current version
- If a legitimately new column is being rejected, update beads so its validator allowlists the new schema
Example fix
// before: peer schema adds column `old-id` (invalid identifier) -- merge produces conflict row with column old-id -> refused // after: rename the column to a valid identifier on the peer branch ALTER TABLE issues RENAME COLUMN `old-id` TO `old_id`;
Defensive patterns
Strategy: validation
Validate before calling
// Reject invalid column names before merging branches
var validIdentifier = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
for _, col := range issueColumns {
if !validIdentifier.MatchString(col) {
return fmt.Errorf("column %q is not a valid identifier; rename before merging", col)
}
} Try / catch
err := versioncontrolops.TryAutoResolveMergeConflicts(ctx, db)
if err != nil {
var colErr error
if strings.Contains(err.Error(), "refusing to write unexpected column") {
// identifier gate tripped: align schemas / resolve manually
return manualResolve(ctx, db)
}
return err
} Prevention
- Name all issues-table columns as plain SQL identifiers (letters, digits, underscore)
- Avoid renaming or adding columns with reserved/odd characters on peer branches
- Keep both repositories on the same beads schema version before merging
- Update beads when the canonical schema gains new columns so the validator allowlists them
When it happens
Trigger: Auto-resolving issues conflicts when the merge plan contains a column name that fails identifier validation — a peer branch added a column with a name outside the allowed identifier set (odd characters, reserved words, unexpected new columns from a schema merge).
Common situations: Merging from a peer running a newer/modified schema whose new issues columns appear in conflict rows; a column named with backticks/quotes or non-identifier characters introduced by another tool.
Related errors
- failed to get conflicts: %w
- failed to scan conflict: %w
- query conflicts for table %s: %w
- conflict columns for table %s: %w
- scan conflict row for table %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/947987a484cd8991.
Report an issue: GitHub.