gastownhall/beads · critical

unexpected conflict row with no issue id (safety check bypas

Error message

unexpected conflict row with no issue id (safety check bypassed)

What it means

resolveIssuesFieldMerge refuses to act on a merge plan entry whose our-side issue id is nil. The planning pass (issuesConflictsAreFieldMergeable) is supposed to decline rows without a key, so reaching this point means a safety invariant was violated — writing an UPDATE with a NULL key would resolve the wrong or no row. This is an internal-inconsistency guard, not an expected user-facing condition.

Source

Thrown at internal/storage/versioncontrolops/automerge.go:583

//
// DOLT_CONFLICTS_RESOLVE is table-level (--ours/--theirs), which cannot express
// a per-cell merge, so this uses dolt's manual-resolution path: write the
// merged values over our working-set row, then DELETE the conflict row — the
// delete is what tells dolt the row is settled, so it must come last. A row
// whose merge equals our side needs no write at all.
func resolveIssuesFieldMerge(ctx context.Context, db DBConn, plan []issuesRowMerge) error {
	for _, m := range plan {
		if len(m.lww) > 0 {
			// Both sides edited these cells since the merge base, so one
			// 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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Do not force-resolve; this error indicates the safety pre-checks were bypassed
  2. Resolve the merge manually with dolt table-level resolution (--ours/--theirs) after inspecting dolt_conflicts_issues
  3. File a bug with the conflict-row contents; the planner should have declined this row
  4. Check for beads/dolt version mismatch and align versions
Defensive patterns

Strategy: fallback

Validate before calling

// Inspect the live conflict rows before trusting auto-resolve
rows, err := db.QueryContext(ctx,
    "SELECT our_id FROM dolt_conflicts_issues")
if err != nil { return err }
for rows.Next() {
    var id any
    _ = rows.Scan(&id)
    if id == nil {
        // delete/modify conflict shape: use manual resolution
        return manualResolve(ctx, db)
    }
}
rows.Close()

Type guard

func planHasNilKey(plan []issuesRowMerge) bool {
    for _, m := range plan {
        if m.ourKey == nil { return true }
    }
    return false
}

Try / catch

err := versioncontrolops.TryAutoResolveMergeConflicts(ctx, db)
if err != nil && strings.Contains(err.Error(), "no issue id (safety check bypassed)") {
    // invariant broken: resolve manually with --ours/--theirs and report a bug
    return manualResolve(ctx, db)
}

Prevention

When it happens

Trigger: Auto-resolving issues-table merge conflicts when a conflict row passed planning with a NULL 'our_id' — i.e. a delete/modify conflict row whose our-side is entirely NULL slipped past declineDuplicateConflictRows/planning, or an internal bug in plan construction.

Common situations: Merging a branch where an issue was deleted on one side and modified on the other (delete/modify conflicts) combined with an unexpected conflict-row shape; potential version mismatch between the planning and resolution code paths.

Related errors


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