gastownhall/beads · error

get labels: %w

Error message

get labels: %w

What it means

GetLabelsInTx returns this when the SELECT of labels for an issue fails at query time. The query runs against either 'labels' or 'wisp_labels', chosen via WispTableRouting based on whether the issue is an active wisp. The library wraps the raw driver error so callers can identify the label-fetch step in hydration call chains (ExecuteCreate, getIssueFromTableInTx, HydrateIssueOperationResult).

Source

Thrown at internal/storage/issueops/labels.go:22

	"context"
	"fmt"
	"strings"

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

// GetLabelsInTx retrieves all labels for an issue within an existing transaction.
// Automatically routes to wisp_labels if the ID is an active wisp.
// Returns labels sorted alphabetically.
func GetLabelsInTx(ctx context.Context, tx DBTX, table, issueID string) ([]string, error) {
	if table == "" {
		isWisp := IsActiveWispInTx(ctx, tx, issueID)
		_, table, _, _ = WispTableRouting(isWisp)
	}
	//nolint:gosec // G201: table is from WispTableRouting ("labels" or "wisp_labels")
	rows, err := tx.QueryContext(ctx, fmt.Sprintf(`SELECT label FROM %s WHERE issue_id = ? ORDER BY label`, table), issueID)
	if err != nil {
		return nil, fmt.Errorf("get labels: %w", err)
	}
	defer rows.Close()

	var labels []string
	for rows.Next() {
		var label string
		if err := rows.Scan(&label); err != nil {
			return nil, fmt.Errorf("get labels: scan: %w", err)
		}
		labels = append(labels, label)
	}
	return labels, rows.Err()
}

// GetLabelsForIssuesInTx fetches labels for multiple issues in a single transaction.
// Routes each ID to labels or wisp_labels based on wisp status.
// Uses a single batched wisp-partition query plus batched IN clauses per label
// table, so the number of round-trips is O(1 + N/queryBatchSize) rather than

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run schema migration (bd upgrade/migrate) to ensure labels and wisp_labels tables exist
  2. Verify the issue ID format is a valid bead ID
  3. Check the wrapped driver error for 'table doesn't exist' vs connection errors and act accordingly
Defensive patterns

Strategy: try-catch

Validate before calling

var exists int
_ = db.QueryRow(`SELECT COUNT(*) FROM information_schema.tables WHERE table_name IN ('labels','wisp_labels')`).Scan(&exists)
if exists < 2 { return errors.New("label tables missing; run bd migrate") }

Try / catch

labels, err := issueops.GetLabelsInTx(ctx, tx, table, issueID)
if err != nil {
    if strings.Contains(err.Error(), "doesn't exist") {
        // run schema migration, then retry
    }
    return fmt.Errorf("hydrate labels: %w", err)
}

Prevention

When it happens

Trigger: Calling GetLabelsInTx (directly or via issue hydration) when the labels table does not exist, the issue ID column mismatch causes a SQL error, or the transaction's connection fails while executing the query.

Common situations: Running against a database initialized by an older schema version missing 'labels' or 'wisp_labels'; corrupted routing where table name is invalid; connection failures inside long transactions.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


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