gastownhall/beads · error
wisp not found: %s
Error message
wisp not found: %s
What it means
The wisp rename UPDATE affected zero rows: no wisp with oldID existed in the wisps table even though IsActiveWispInTx initially routed the rename there. The library surfaces this as "wisp not found" and aborts the transaction with no changes.
Source
Thrown at internal/storage/issueops/bulk_ops.go:337
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)
}
// FindWispDependentsRecursiveInTx walks wisp_dependencies to find all transitive
// dependents of the given IDs.
func FindWispDependentsRecursiveInTx(ctx context.Context, tx DBTX, ids []string) (map[string]bool, error) {View on GitHub (pinned to 71377f2769)
Solutions
- Re-fetch the item by ID to determine whether it is now an issue, a wisp, or gone, then retry accordingly.
- Verify the exact ID (typos, case) before renaming.
- Sync latest state from remote (bd dolt pull / sync) and retry.
- Handle this as a transient race: retry the whole rename operation once.
Example fix
// before
store.UpdateIssueID(ctx, wispID, newID, issue, actor) // wisp reaped concurrently -> not found
// after
if err := refreshAndRename(ctx, store, wispID, newID); err != nil {
var nf *NotFoundError
if errors.As(err, &nf) { return refreshAndRename(ctx, store, wispID, newID) } // retry after re-check
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
iss, err := store.GetIssue(ctx, oldID)
if err != nil || iss == nil {
return fmt.Errorf("id %s no longer exists (wisp reaped or typo); refresh state", oldID)
} Try / catch
err := store.UpdateIssueID(ctx, oldID, newID, issue, actor)
if err != nil && strings.HasPrefix(err.Error(), "wisp not found:") {
// wisp was compacted/promoted between check and update: re-resolve and retry once
return refreshAndRetry(ctx, store, oldID, newID)
} Prevention
- Treat wisp renames as racy with compaction; re-fetch just before renaming.
- Sync remote state before operating on wisp IDs.
- Retry once on not-found before surfacing to the user.
When it happens
Trigger: Calling UpdateIssueIDInTx where oldID was detected as an active wisp but the `UPDATE wisps ... WHERE id = oldID` matched nothing — the wisp was reaped/compacted between the check and the update, or the ID was typed incorrectly.
Common situations: Race with wisp compaction (wisp promoted to a real issue or deleted concurrently); stale ID from another machine before sync; typo in the ID passed to `bd rename`.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e43bfd3cfcc66b51.
Report an issue: GitHub.