gastownhall/beads · error
update wisp ID: %w
Error message
update wisp ID: %w
What it means
This error wraps a failure of the SQL UPDATE that renames a wisp row (`UPDATE wisps SET id = ? ...`). Wisps are ephemeral derived issues stored in a separate table; when the UPDATE fails at the driver level the rename transaction aborts and the wisp keeps its old ID.
Source
Thrown at internal/storage/issueops/bulk_ops.go:334
return InsertDerivedEvent(ctx, tx, "events", AuxEvent{
IssueID: newID,
EventType: "renamed",
Actor: actor,
OldValue: str(oldID),
NewValue: str(newID),
})
}
func updateWispIDInTx(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 wisps
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 wisp ID: %w", err)
}
if rows, _ := result.RowsAffected(); rows == 0 {
return fmt.Errorf("wisp not found: %s", oldID)
}
if err = InsertDerivedEvent(ctx, tx, "wisp_events", AuxEvent{
IssueID: newID,
EventType: "renamed",
Actor: actor,
OldValue: str(oldID),
NewValue: str(newID),
}); err != nil {
return err
}
return UpdateWispIDInDependenciesInTx(ctx, tx, oldID, newID)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Ensure newID does not collide with an existing wisp or issue before renaming.
- Validate the new ID format (non-empty, allowed characters).
- Inspect the wrapped driver error for the exact constraint and fix the input.
- Bring schema/migrations up to date and retry in a fresh transaction.
Example fix
// before
store.UpdateIssueID(ctx, oldID, dupID, issue, actor) // dupID exists in wisps -> constraint violation
// after
if err := ensureIDFree(ctx, store, dupID); err != nil {
return fmt.Errorf("choose a different ID: %w", err)
}
store.UpdateIssueID(ctx, oldID, dupID, issue, actor) Defensive patterns
Strategy: validation
Validate before calling
func validateWispRename(ctx context.Context, store Store, newID string) error {
if newID == "" { return errors.New("new id must not be empty") }
if existing, _ := store.GetIssue(ctx, newID); existing != nil { return fmt.Errorf("id %s already in use", newID) }
return nil
} Try / catch
if err := store.UpdateIssueID(ctx, oldID, newID, issue, actor); err != nil {
if strings.Contains(err.Error(), "update wisp ID:") {
return fmt.Errorf("wisp rename rejected (check ID collision/format): %w", err)
}
return err
} Prevention
- Validate target ID format and uniqueness for both issues and wisps.
- Keep wisp/aux schema migrations in sync with the binary version.
- Retry aborted renames; the transaction guarantees no partial rename.
When it happens
Trigger: Calling UpdateIssueIDInTx on an issue that IsActiveWispInTx reports as a wisp, and `UPDATE wisps` fails — unique-key collision on newID in wisps, invalid newID, or a driver/connection error.
Common situations: Renaming to an ID that already exists as another wisp; wisp table schema drift after upgrade; connection loss mid-transaction; malformed target ID rejected by the storage engine.
Related errors
- update issue ID: %w
- wisp not found: %s
- 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/92fb50672eb0cbf4.
Report an issue: GitHub.