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 thanView on GitHub (pinned to 71377f2769)
Solutions
- Run schema migration (bd upgrade/migrate) to ensure labels and wisp_labels tables exist
- Verify the issue ID format is a valid bead ID
- 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
- Run schema migrations before opening issues against a database
- Always pass a valid table name or empty string to trigger routing
- Validate issue IDs before hydration
- Check wrapped errors for 'table doesn't exist' vs connection failures
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
- failed to recompute is_blocked: %w
- failed to scan federation peer: %w
- failed to get wisp labels: %w
- hydrate labels: %w
- get labels from %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f369c9e5ba2e3852.
Report an issue: GitHub.