gastownhall/beads · error

failed to update issue: %w

Error message

failed to update issue: %w

What it means

The UPDATE statement executed inside updateIssueInTx failed at the database driver level. The driver error is wrapped as "failed to update issue: %w", and the transaction will typically be rolled back by the caller.

Source

Thrown at internal/storage/issueops/update.go:490

	// Auto-manage leases when direct updates change status or assignee.
	// Clears stale leases only; arming is reserved for claim/heartbeat.
	clearLease := ManageLeaseOnUpdate(oldIssue, updates)

	// Rewrite row_lock on every update so a concurrent status/ownership
	// mutation (reclaim/close) collides on this shared cell and is forced to
	// conflict-and-retry rather than silently cell-merging two writes to
	// different columns of the same row (see lease.go). This is the "every
	// mutating path writes row_lock" invariant the lease scheme depends on.
	setClauses = append(setClauses, "row_lock = ?")
	args = append(args, freshRowLock())

	args = append(args, id)

	//nolint:gosec // G201: issueTable comes from WispTableRouting (hardcoded constants)
	query := fmt.Sprintf("UPDATE %s SET %s WHERE id = ?", issueTable, strings.Join(setClauses, ", "))
	if _, err := tx.ExecContext(ctx, query, args...); err != nil {
		return nil, fmt.Errorf("failed to update issue: %w", err)
	}

	if clearLease {
		if err := DeleteLeaseInTx(ctx, tx, id); err != nil {
			return nil, err
		}
	}

	if recordEvent {
		oldData, _ := json.Marshal(oldIssue)
		newData, _ := json.Marshal(updates)
		eventType := DetermineEventType(oldIssue, updates)

		if err := RecordFullEventInTable(ctx, tx, eventTable, id, eventType, actor, string(oldData), string(newData)); err != nil {
			return nil, fmt.Errorf("failed to record event: %w", err)
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped driver error for the SQL state/constraint message.
  2. Verify all update values match column types and constraints (run the equivalent UPDATE manually).
  3. Check database connectivity and retry the transaction if the error is transient (connection refused/timeout).
  4. Confirm the schema matches the version of the code (run migrations/bd doctor).

Example fix

// before
if _, err := tx.ExecContext(ctx, query, args...); err != nil {
    return nil, fmt.Errorf("failed to update issue: %w", err) // opaque
}
// after
if _, err := tx.ExecContext(ctx, query, args...); err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return nil, err // retryable: caller re-runs tx
    }
    return nil, fmt.Errorf("failed to update issue: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-check values against schema constraints before the call
if s, ok := updates["status"]; ok {
    if !types.IsValidStatus(fmt.Sprintf("%v", s)) {
        return fmt.Errorf("invalid status %v", s)
    }
}

Try / catch

if _, err := storage.UpdateIssue(ctx, id, updates, actor); err != nil {
    if strings.Contains(err.Error(), "failed to update issue") {
        var retriable = errors.Is(err, context.DeadlineExceeded) || isConnectionError(err)
        if retriable { /* reopen tx and retry */ }
    }
    return err
}

Prevention

When it happens

Trigger: tx.ExecContext fails: constraint violations (e.g. NOT NULL, CHECK on status), value type mismatches with the column schema, table lock/timeout, connection loss, or an invalid value for a newly-allowed field not matching the schema.

Common situations: Setting a column to a type the driver can't bind; Dolt server restart or dropped connection mid-transaction; schema drift where a column is stricter than the code expects; unique/PK conflicts via dependent tables.

Related errors


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