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

  1. Identify the offending column from the %q in the message
  2. Normalize the schema across branches (align column names to valid identifiers) and re-merge
  3. Resolve the affected conflicts manually with DOLT_CONFLICTS_RESOLVE, then update beads/schema to the current version
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/947987a484cd8991. Report an issue: GitHub.