gastownhall/beads · error
get issues by IDs from %s: %w
Error message
get issues by IDs from %s: %w
What it means
Wraps a QueryContext error while fetching issue rows by ID from one of the issue tables (permanent or wisp) during GetIssuesByIDsInTx. The table name is embedded in the message (%s) so you know which of the paired tables the SELECT failed against.
Source
Thrown at internal/storage/issueops/dependencies.go:1048
end := start + queryBatchSize
if end > len(pair.ids) {
end = len(pair.ids)
}
batch := pair.ids[start:end]
placeholders := make([]string, len(batch))
args := make([]any, len(batch))
for i, id := range batch {
placeholders[i] = "?"
args[i] = id
}
inClause := strings.Join(placeholders, ",")
rows, err := tx.QueryContext(ctx, fmt.Sprintf(
`SELECT %s FROM %s %s WHERE id IN (%s)`,
IssueSelectColumns, pair.table, sqlbuild.LeaseJoin(pair.table), inClause), args...)
if err != nil {
return nil, fmt.Errorf("get issues by IDs from %s: %w", pair.table, err)
}
issueMap := make(map[string]*types.Issue)
for rows.Next() {
issue, scanErr := ScanIssueFrom(rows)
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Batch the ID list into chunks if it is very large (placeholder limit)
- Check the wrapped driver error and the named table exists (run migrations)
- Retry on transient connection/lock errors
- Verify IssueSelectColumns/LeaseJoin SQL fragments are compatible with your driver version
Defensive patterns
Strategy: validation
Validate before calling
// Validate inputs and DB readiness before the batch fetch:
if len(ids) == 0 { return nil }
if len(ids) > maxPlaceholders { ids = chunk(ids, maxPlaceholders) } // avoid placeholder overflow
if err := txErr(tx); err != nil { return err }
// Confirm the issue tables exist:
rows, err := db.QueryContext(ctx, `SELECT name FROM sqlite_master WHERE type='table' AND name IN ('issues','wisps')`) Type guard
func validIDList(ids []string) bool {
for _, id := range ids {
if id == "" || len(id) > maxIDLen { return false }
}
return len(ids) > 0 && len(ids) <= maxPlaceholders
} Try / catch
issues, err := GetIssuesByIDsInTx(ctx, tx, ids, nil)
var qErr *IssueFetchError
if err != nil {
if strings.Contains(err.Error(), "get issues by IDs from") && isTransientDBError(err) {
issues, err = GetIssuesByIDsInTx(ctx, tx, ids, nil) // retry transient
}
if err != nil { return err }
} Prevention
- Chunk very large ID lists to stay under driver placeholder limits
- Run migrations before upgrading the binary (tables must match code)
- Use context timeouts longer than expected query duration
- Validate IDs are non-empty before calling batch fetches
When it happens
Trigger: The parameterized `SELECT ... FROM <issues|wisps> ... WHERE id IN (...)` query returns a driver error — bad SQL, missing table, connection failure, or too many placeholders for a huge ID list.
Common situations: Very large ID lists exceeding driver placeholder limits, missing migration (table absent), connection dropped mid-transaction, or SQL syntax drift after a driver upgrade.
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/cddd13abd7b544af.
Report an issue: GitHub.