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
- Check that newID does not already exist before renaming (SELECT or the library's Exists helper).
- Validate the new ID format (prefix, allowed characters, non-empty) before calling UpdateIssueID.
- Read the wrapped driver error to identify the exact constraint and address it.
- 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
- Always check target ID uniqueness before renaming.
- Enforce ID format validation (prefix + digits) in your tooling.
- Retry failed renames in a fresh transaction; partial state is rolled back.
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
- update wisp ID: %w
- insert issue into %s: %w
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/33e23f665d54539a.
Report an issue: GitHub.