gastownhall/beads · error

update issue ID: %w

Error message

update issue ID: %w

What it means

This error means the SQL UPDATE that moves an issue row to its new ID (and rewrites its fields) failed at the driver level. It wraps the raw database error, so the cause is typically a constraint violation or a connection problem. The rename transaction is aborted, leaving the old ID intact.

Source

Thrown at internal/storage/issueops/bulk_ops.go:301

		if target == oldID {
			target = newID
		}
		if err := RecordDepEventInTx(ctx, tx, EventDepAdd, source, edge.kind, target, edge.metadata, actor); err != nil {
			return err
		}
	}
	return nil
}

func updateIssueIDInTx(ctx context.Context, tx *sql.Tx, oldID, newID string, issue *types.Issue, actor string) error {
	now := time.Now().UTC()
	result, err := tx.ExecContext(ctx, `
		UPDATE issues
		SET id = ?, title = ?, description = ?, design = ?, acceptance_criteria = ?, notes = ?, updated_at = ?
		WHERE id = ?
	`, newID, issue.Title, issue.Description, issue.Design, issue.AcceptanceCriteria, issue.Notes, now, oldID)
	if err != nil {
		return fmt.Errorf("update issue ID: %w", err)
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return fmt.Errorf("issue not found: %s", oldID)
	}

	if err := UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
		return err
	}

	// A live lease follows its issue across the rename.
	if _, err := tx.ExecContext(ctx,
		`UPDATE leases SET issue_id = ? WHERE issue_id = ?`, newID, oldID); err != nil {
		return fmt.Errorf("rename lease row: %w", err)
	}

	return InsertDerivedEvent(ctx, tx, "events", AuxEvent{
		IssueID:   newID,
		EventType: "renamed",

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check that newID does not already exist before renaming (SELECT or the library's Exists helper).
  2. Validate the new ID format (prefix, allowed characters, non-empty) before calling UpdateIssueID.
  3. Read the wrapped driver error to identify the exact constraint and address it.
  4. Ensure schema migrations are current, then retry the rename in a fresh transaction.

Example fix

// before
store.UpdateIssueID(ctx, "bd-1", "bd-2", issue, actor) // bd-2 already exists -> constraint violation
// after
if exists, _ := store.GetIssue(ctx, "bd-2"); exists != nil {
    return fmt.Errorf("cannot rename: %s already exists", "bd-2")
}
store.UpdateIssueID(ctx, "bd-1", "bd-2", issue, actor)
Defensive patterns

Strategy: validation

Validate before calling

func validateRename(ctx context.Context, store Store, oldID, newID string) error {
    if newID == "" || strings.ContainsAny(newID, " \t/") { return fmt.Errorf("invalid new id %q", newID) }
    if existing, _ := store.GetIssue(ctx, newID); existing != nil { return fmt.Errorf("id %s already exists", newID) }
    return nil
}

Try / catch

if err := store.UpdateIssueID(ctx, oldID, newID, issue, actor); err != nil {
    if strings.Contains(err.Error(), "update issue ID:") {
        var driverErr *SQLError // or driver-specific type
        if errors.As(err, &driverErr) && isConstraintViolation(driverErr) {
            return fmt.Errorf("target id %s conflicts with an existing row", newID)
        }
    }
    return err
}

Prevention

When it happens

Trigger: Calling UpdateIssueIDInTx where `UPDATE issues SET id = ? ...` fails — e.g. newID already exists (primary-key/unique violation), newID empty or malformed violating a CHECK, or the driver errors on the statement.

Common situations: Renaming to an ID that collides with an existing issue; renaming to an ID containing invalid characters under a strict schema; DB connection dropped mid-transaction; schema out of sync with the code.

Related errors


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