gastownhall/beads · error

wisp %s not found

Error message

wisp %s not found

What it means

PromoteFromEphemeralInTx first checks IsActiveWispInTx; if the ID is not currently an active wisp, it fails immediately with 'wisp %s not found'. Promotion only applies to live ephemeral (wisp-plane) issues, so any ID that is absent, already promoted, or fully deleted produces this error.

Source

Thrown at internal/storage/issueops/promote.go:14

package issueops

import (
	"context"
	"fmt"

	"github.com/steveyegge/beads/internal/storage"
	"github.com/steveyegge/beads/internal/types"
)

//nolint:gosec // G201: table names are hardcoded constants
func PromoteFromEphemeralInTx(ctx context.Context, tx DBTX, id string, actor string) error {
	if !IsActiveWispInTx(ctx, tx, id) {
		return fmt.Errorf("wisp %s not found", id)
	}

	issue, err := GetIssueInTx(ctx, tx, id)
	if err != nil {
		return fmt.Errorf("get wisp for promote: %w", err)
	}
	if issue == nil {
		return fmt.Errorf("wisp %s not found", id)
	}

	// A promoted wisp is fully durable: clear BOTH wisp-plane flags, not just
	// Ephemeral. A no-history wisp (Ephemeral=false, NoHistory=true) promoted
	// with NoHistory intact lands in the issues table still flag-marked as
	// wisp-plane state, and everything that infers the plane from flags —
	// most damagingly import's table routing — silently re-planes it back
	// into the (default-export-excluded) wisps table, dropping its relations
	// on the way (bd-r9uce). Post-promotion the flag has no meaning.
	issue.Ephemeral = false

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the issue's current plane/state before promoting (query the wisp table or use a read API) and skip already-promoted IDs
  2. Treat this as idempotent success if the ID now exists as a durable issue — the promote already happened
  3. Re-check the ID for typos or stale references; run bd list to find current wisp IDs
  4. If the wisp was created in another clone, sync first (bd dolt pull)

Example fix

// before
if err := issueops.PromoteFromEphemeralInTx(ctx, tx, id, actor); err != nil {
	return err
}
// after
if err := issueops.PromoteFromEphemeralInTx(ctx, tx, id, actor); err != nil {
	if strings.Contains(err.Error(), "not found") && issueExistsDurable(ctx, tx, id) {
		return nil // already promoted; idempotent retry
	}
	return err
}
Defensive patterns

Strategy: validation

Validate before calling

active, err := isActiveWisp(ctx, tx, id) // check the wisp plane first
if err != nil { return err }
if !active { return fmt.Errorf("skip %s: not an active wisp", id) }

Type guard

func isActiveWisp(ctx context.Context, tx issueops.DBTX, id string) (bool, error)
// plus: func isDurableIssue(ctx context.Context, tx issueops.DBTX, id string) (bool, error)
// to distinguish 'already promoted' from 'never existed'

Try / catch

if err := issueops.PromoteFromEphemeralInTx(ctx, tx, id, actor); err != nil {
	if isWispNotFound(err) {
		if exists, _ := isDurableIssue(ctx, tx, id); exists {
			return nil // idempotent: already promoted
		}
		return fmt.Errorf("wisp %s does not exist", id)
	}
	return err
}

Prevention

When it happens

Trigger: Calling PromoteFromEphemeralInTx with an ID that is not an active wisp: the issue does not exist, was already promoted to the durable plane, was deleted, or exists only as a durable (non-ephemeral) issue.

Common situations: Double-promotion after a retry that actually succeeded the first time; promoting an ID copied from the issues table instead of the wisp table; a stale script referencing wisps that were garbage-collected.

Related errors


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