gastownhall/beads · error

read wisp %s: %w

Error message

read wisp %s: %w

What it means

operationIssue first tries the wisp plane via GetWisp; if the wisp read fails with an error that is neither ErrNotFound nor a no-rows error, the operation aborts and this error wraps the underlying failure. It signals an unexpected storage-layer failure while reading the ephemeral (wisp) copy of an issue, not a simple 'does not exist' case.

Source

Thrown at internal/storage/uow/issue_operations.go:502

func updateHistoryEntry(request publicops.UpdateRequest, changed bool) string {
	if !changed && request.Claim && reflect.DeepEqual(request.Patch, publicops.IssuePatch{}) {
		return ""
	}
	return storageissueops.HistoryEntry(request.Provenance, "update issue")
}

// operationIssue resolves id to the row an operation is about. Both planes are
// searched unless issuePlaneOnly restricts it, in which case a wisp id is a
// miss rather than an ephemeral row to operate on. Every call runs inside the
// operation's own transaction.
func operationIssue(ctx context.Context, uw UnitOfWork, id string, issuePlaneOnly bool) (*types.Issue, bool, error) {
	if !issuePlaneOnly {
		issue, err := uw.IssueUseCase().GetWisp(ctx, id)
		if err == nil && issue != nil {
			return issue, true, nil
		}
		if err != nil && !errors.Is(err, publicops.ErrNotFound) && !dberrors.IsNoRows(err) {
			return nil, false, fmt.Errorf("read wisp %s: %w", id, err)
		}
	}
	issue, err := uw.IssueUseCase().GetIssue(ctx, id)
	if err != nil {
		if errors.Is(err, publicops.ErrNotFound) || dberrors.IsNoRows(err) {
			return nil, false, fmt.Errorf("%w: issue %s", publicops.ErrNotFound, id)
		}
		return nil, false, err
	}
	if issue == nil {
		return nil, false, fmt.Errorf("%w: issue %s", publicops.ErrNotFound, id)
	}
	return issue, false, nil
}

func validationError(err error) error {
	if errors.Is(err, publicops.ErrValidation) {
		return err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause (errors.Unwrap / %w chain) to identify the underlying storage error
  2. Verify database connectivity and that the Dolt DB is healthy (bd doctor / driver health)
  3. Retry the operation if the failure was transient (connection reset, lock timeout)
  4. Confirm the storage schema matches the expected version; run migrations if behind
  5. If it persists, file an issue with the wrapped error and operation context

Example fix

// before: treating any wisp error as fatal
if err != nil { return err }
// after: only abort on non-not-found errors, matching operationIssue semantics
if err != nil && !errors.Is(err, publicops.ErrNotFound) && !dberrors.IsNoRows(err) {
    return fmt.Errorf("read wisp %s: %w", id, err)
}
// not-found falls through to the durable GetIssue read
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check database reachability before the operation
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("storage unavailable: %w", err) }

Try / catch

if _, _, err := op.OperationIssue(ctx, id); err != nil {
    var nf *fmt.wrapError
    if errors.Is(err, publicops.ErrNotFound) { /* fall back */ }
    else if isTransient(err) { /* retry with backoff */ }
    else { return fmt.Errorf("wisp read failed: %w", err) }
}

Prevention

When it happens

Trigger: Calling Update/Close/Reopen/hydrateIssueOperation when issuePlaneOnly is false and uw.IssueUseCase().GetWisp returns a genuine error (DB corruption, connection failure, driver error) rather than not-found.

Common situations: Dolt/driver connectivity problems, locked or corrupted database, schema drift between planes, or a bug in the wisp use case that returns a non-sentinel error during normal issue lookup flows.

Related errors


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