gastownhall/beads · error

resolving %s: %w

Error message

resolving %s: %w

What it means

When `workapi.GetIssueOrWisp` fails with an error other than `storage.ErrNotFound`, `uowMolReader.GetIssue` wraps it as `resolving %s: %w`, preserving the underlying cause. This distinguishes 'ID simply absent' (1086) from 'lookup failed' — storage errors, malformed ID resolution, or transaction failures inside the unit of work.

Source

Thrown at cmd/bd/mol_port.go:130

		return tx.UpdateIssue(ctx, id, map[string]interface{}{"status": types.StatusInProgress}, actor)
	})
}

func newStandaloneStoreMolWriter(store storage.DoltStorage) storeMolWriter {
	return storeMolWriter{DoltStorage: store}
}

type uowMolReader struct {
	uw uow.UnitOfWork
}

func (r uowMolReader) GetIssue(ctx context.Context, id string) (*types.Issue, error) {
	issue, isWisp, rerr := workapi.GetIssueOrWisp(ctx, workapi.NewUOWDetailSource(r.uw), id)
	if errors.Is(rerr, storage.ErrNotFound) {
		return nil, fmt.Errorf("issue %s not found", id)
	}
	if rerr != nil {
		return nil, fmt.Errorf("resolving %s: %w", id, rerr)
	}
	// READS, ALL OF THEM, and they stay for the reason the writes did not.
	// uowMolReader is a PORT: it adapts a caller's open unit of work to the
	// molecule commands' reader interface, and every method here must answer
	// from inside that transaction. issueops.Reader opens one of its own, so a
	// role-routed port would show the molecule the last committed state while
	// the command that owns the transaction is midway through changing it.
	// A reader role bound to a caller's transaction is the follow-up
	// (ga-2ltro.12). The wisp branch here is a read that follows the row
	// GetIssueOrWisp already found, not a front door choosing where to write.
	var labels []string
	var err error
	if isWisp {
		labels, err = r.uw.LabelUseCase().GetWispLabels(ctx, id) //nolint:forbidigo // in-transaction port read; issueops.Reader would open its own
	} else {
		labels, err = r.uw.LabelUseCase().GetLabels(ctx, id) //nolint:forbidigo // in-transaction port read; issueops.Reader would open its own
	}
	if err == nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause (unwrap `%w`) — fix the underlying storage/transaction error it reports.
  2. Re-run `bd doctor` to check database health and connectivity.
  3. Ensure reads happen while the caller's unit of work is open and valid; recreate the reader if the transaction was closed.
  4. Retry the command if the storage error was transient (e.g. lock contention).

Example fix

// before
issue, err := reader.GetIssue(ctx, id) // opaque failure
// after
issue, err := reader.GetIssue(ctx, id)
if err != nil && strings.HasPrefix(err.Error(), "resolving ") {
    log.Printf("lookup failed: %v", errors.Unwrap(err)) // see real cause
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check storage health before molecule reads
bd doctor --json | jq -e '.healthy' >/dev/null || { echo "database unhealthy"; exit 1; }

Try / catch

iss, err := reader.GetIssue(ctx, id)
if err != nil {
	var cause error = err
	for errors.Unwrap(cause) != nil {
		cause = errors.Unwrap(cause)
	}
	if errors.Is(cause, storage.ErrNotFound) {
		return nil
	}
	return fmt.Errorf("transient lookup failure, retry: %w", err)
}

Prevention

When it happens

Trigger: Any non-NotFound error from the resolver while `GetMoleculeProgress`, `GetMoleculeLastActivity`, `runMolBurnProxiedServer`, or `runWispCreateProxiedServer` reads an ID: database I/O failure, closed/invalid unit of work, corrupted storage, or driver-level errors surfaced by `GetIssueOrWisp`.

Common situations: Dolt database unavailable or locked; calling reader methods outside a valid transaction lifetime; disk/permission problems on the storage backend; transient driver failures during heavy parallel reads.

Related errors


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