gastownhall/beads · error
get issues by IDs: labels from %s: %w
Error message
get issues by IDs: labels from %s: %w
What it means
Wraps a QueryContext error while hydrating labels for the fetched issues in GetIssuesByIDsInTx. The issue rows loaded fine but the follow-up SELECT against the label table (permanent or wisp labels) failed.
Source
Thrown at internal/storage/issueops/dependencies.go:1071
if scanErr != nil {
_ = rows.Close()
return nil, fmt.Errorf("get issues by IDs: scan: %w", scanErr)
}
allIssues = append(allIssues, issue)
issueMap[issue.ID] = issue
}
_ = rows.Close()
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("get issues by IDs: rows: %w", err)
}
// Hydrate labels.
if len(issueMap) > 0 {
labelRows, err := tx.QueryContext(ctx, fmt.Sprintf(
`SELECT issue_id, label FROM %s WHERE issue_id IN (%s) ORDER BY issue_id, label`,
pair.labelTbl, inClause), args...)
if err != nil {
return nil, fmt.Errorf("get issues by IDs: labels from %s: %w", pair.labelTbl, err)
}
for labelRows.Next() {
var issueID, label string
if scanErr := labelRows.Scan(&issueID, &label); scanErr != nil {
_ = labelRows.Close()
return nil, fmt.Errorf("get issues by IDs: scan label: %w", scanErr)
}
if issue, ok := issueMap[issueID]; ok {
issue.Labels = append(issue.Labels, label)
}
}
_ = labelRows.Close()
if err := labelRows.Err(); err != nil {
return nil, fmt.Errorf("get issues by IDs: label rows: %w", err)
}
}
}
}View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped driver error and confirm the named label table exists (migrations)
- Batch large ID lists to stay under placeholder limits
- Retry on transient errors; the operation is read-only and safe to re-run
- Verify driver/schema version alignment after upgrades
Defensive patterns
Strategy: validation
Validate before calling
// Confirm both label tables exist before ID-batch fetches:
for _, tbl := range []string{"labels", "wisp_labels"} {
var n int
err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = ?`, tbl).Scan(&n)
if err != nil || n == 0 { return fmt.Errorf("missing label table %s; run migrations", tbl) }
} Try / catch
issues, err := GetIssuesByIDsInTx(ctx, tx, ids, nil)
if err != nil && strings.Contains(err.Error(), "labels from") {
if isMissingTable(err) { return runMigrationsThenRetry(ctx) }
if isTransientDBError(err) { issues, err = GetIssuesByIDsInTx(ctx, tx, ids, nil) }
if err != nil { return err }
} Prevention
- Run full migrations (wisp_labels included), not partial ones
- Batch ID lists to avoid placeholder overflow on the label query
- Treat label hydration as re-runnable — safe to retry read-only fetches
- Keep driver version aligned with the SQL fragments in the code
When it happens
Trigger: The `SELECT issue_id, label FROM <labels|wisp_labels> WHERE issue_id IN (...)` query errors — missing label table, driver error, connection failure, or oversized IN clause.
Common situations: Partial migrations where wisp_labels was not created; connection drop between the issue query and label query; very large ID lists hitting placeholder limits.
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 begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
- failed to query orphaned dependencies: %w
- row iteration error: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/abc915a79f03af03.
Report an issue: GitHub.