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

  1. Check the wrapped driver error and confirm the named label table exists (migrations)
  2. Batch large ID lists to stay under placeholder limits
  3. Retry on transient errors; the operation is read-only and safe to re-run
  4. 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

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


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