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

  1. Re-fetch the item by ID to determine whether it is now an issue, a wisp, or gone, then retry accordingly.
  2. Verify the exact ID (typos, case) before renaming.
  3. Sync latest state from remote (bd dolt pull / sync) and retry.
  4. 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

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.