gastownhall/beads · error

scanning open beads for references: %w

Error message

scanning open beads for references: %w

What it means

This wraps a failure from IssueUseCase().SearchIssues when the sweeper scans all open (not-done) beads to look for references to candidate issues. The sweep contract says an implementation that cannot read the full not-done set must fail rather than under-scan, so any search error aborts the sweep with this message instead of proceeding and potentially deleting still-referenced beads.

Source

Thrown at internal/storage/uow/sweeper.go:139

// candidates came off. Comments are read for the whole not-done set in ONE
// batch rather than one query per row.
func sweepReferencedInUOW(ctx context.Context, uw UnitOfWork, candidates []*types.Issue) (map[string]bool, error) {
	if len(candidates) == 0 {
		return nil, nil
	}
	candidateIDs := make(map[string]bool, len(candidates))
	for _, issue := range candidates {
		candidateIDs[issue.ID] = true
	}
	matcher := workapi.NewCandidateIDMatcher(candidateIDs)

	custom, err := uw.ConfigUseCase().GetCustomStatuses(ctx)
	if err != nil {
		return nil, fmt.Errorf("reading custom statuses for reference scan: %w", err)
	}
	page, err := uw.IssueUseCase().SearchIssues(ctx, "", workapi.BuildSweepReferenceScanFilter(custom))
	if err != nil {
		return nil, fmt.Errorf("scanning open beads for references: %w", err)
	}
	notDone := page.Items

	notDoneIDs := make([]string, 0, len(notDone))
	for _, issue := range notDone {
		if issue != nil {
			notDoneIDs = append(notDoneIDs, issue.ID)
		}
	}
	// BOTH PLANES, and this is not an optimization detail. The not-done set
	// comes from SearchIssues, which merges the durable and wisp planes, so it
	// contains wisps — and their comments live in wisp_comments, which
	// GetCommentsForIssues does not read. Scanning only the durable table left
	// a closed bead cited solely by a comment on an open WISP unprotected, so
	// `bd prune` deleted it on this route and kept it on the other. The
	// contract says an implementation that cannot read the full set must fail
	// the sweep rather than under-scan it.
	//

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped cause for the real SearchIssues error and address it (connectivity, schema, corruption)
  2. Retry the sweep after restoring the database connection; the sweep is safe to re-run
  3. Run a consistency check/repair on the Dolt database if the error persists
  4. Update beads so the issues/wisps schema matches the binary's expectations
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: confirm the issues table is queryable before sweeping
rows, err := db.QueryContext(ctx, "SELECT 1 FROM issues LIMIT 1")
if err != nil {
    return fmt.Errorf("issues table not readable, skip sweep: %w", err)
}
rows.Close()

Try / catch

result, err := sweeper.Sweep(ctx, req)
if err != nil && strings.Contains(err.Error(), "scanning open beads for references") {
    // sweep is idempotent: back off and retry once the DB is healthy
    return retrySweepWithBackoff(ctx, req)
}

Prevention

When it happens

Trigger: bd prune/sweep runs and SearchIssues with the sweep reference scan filter (built from custom statuses) fails — SQL error on the issues/wisps tables, connection loss mid-UOW, or a malformed filter due to unusual custom status values.

Common situations: Dolt server unavailable or timing out on large repos; schema drift between beads versions in the issues table; a corrupted database file after an unclean shutdown.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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