gastownhall/beads · error
db: DependencySQLRepository.GetBlockingInfo: inbound: %w
Error message
db: DependencySQLRepository.GetBlockingInfo: inbound: %w
What it means
Wraps a failure from the inbound scanBlockingRows query in GetBlockingInfo (SELECT ... WHERE depends_on_id IN (...) AND type='blocks'), which computes the BlockedBy map. The outbound half already succeeded; this failure discards everything and returns an empty BlockingInfo with the error.
Source
Thrown at internal/storage/domain/db/dependency.go:512
//nolint:gosec // G201: table and depTargetExpr are hardcoded constants
outQ := fmt.Sprintf(
"SELECT issue_id, %s AS depends_on_id, type FROM %s WHERE issue_id IN (%s) AND type IN ('blocks', 'parent-child')",
depTargetExpr, table, idPlaceholders,
)
outRows, err := r.scanBlockingRows(ctx, outQ, idArgs)
if err != nil {
return domain.BlockingInfo{}, fmt.Errorf("db: DependencySQLRepository.GetBlockingInfo: outbound: %w", err)
}
//nolint:gosec // G201: table and depTargetExpr are hardcoded constants
inQ := fmt.Sprintf(
"SELECT issue_id, %s AS depends_on_id, type FROM %s WHERE %s IN (%s) AND type = 'blocks'",
depTargetExpr, table, depTargetExpr, idPlaceholders,
)
inRows, err := r.scanBlockingRows(ctx, inQ, idArgs)
if err != nil {
return domain.BlockingInfo{}, fmt.Errorf("db: DependencySQLRepository.GetBlockingInfo: inbound: %w", err)
}
statusIDs := make(map[string]struct{})
for _, row := range outRows {
statusIDs[row.dependsOnID] = struct{}{}
}
for _, row := range inRows {
statusIDs[row.dependsOnID] = struct{}{}
}
statusByID, err := r.loadStatusByID(ctx, statusIDs)
if err != nil {
return domain.BlockingInfo{}, fmt.Errorf("db: DependencySQLRepository.GetBlockingInfo: status lookup: %w", err)
}
for _, row := range outRows {
if statusByID[row.dependsOnID] == types.StatusClosed {
continue
}View on GitHub (pinned to 71377f2769)
Solutions
- Retry the whole GetBlockingInfo call; both halves are needed for a consistent result.
- Chunk issueIDs to stay within placeholder limits.
- Verify UseWispsTable points at the correct deps table.
- Check connection health/pool timeouts if the second query repeatedly fails after the first.
Example fix
// before
info, err := deps.GetBlockingInfo(ctx, ids, opts)
if err != nil { return err } // possibly transient (inbound) failure
// after
info, err := retry.Do(ctx, 3, backoff, func() (domain.BlockingInfo, error) {
return deps.GetBlockingInfo(ctx, ids, opts)
}) Defensive patterns
Strategy: retry
Validate before calling
if len(issueIDs) == 0 { return domain.BlockingInfo{}, nil }
if len(issueIDs) > 500 { /* chunk before calling */ } Try / catch
info, err := deps.GetBlockingInfo(ctx, ids, opts)
if err != nil && strings.Contains(err.Error(), "GetBlockingInfo: inbound") {
if isTransientDBError(err) {
info, err = deps.GetBlockingInfo(ctx, ids, opts) // retry whole call
}
} Prevention
- Always retry the entire GetBlockingInfo call so outbound and inbound halves stay consistent.
- Chunk IDs to respect placeholder limits.
- Ensure the connection pool keeps connections alive through multi-query calls.
- Verify the wisps-table flag matches the data location.
When it happens
Trigger: GetBlockingInfo(ctx, issueIDs, opts): the inbound SELECT fails or rows fail to scan — connection dropped between the two queries, placeholder limit exceeded, wrong wisps/issues table, corrupted dep rows.
Common situations: Same bulk blocking-info contexts as outbound, plus mid-call connection loss where only the second query fails.
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
- db: DependencySQLRepository.GetBlockingInfo: outbound: %w
- ErrQuery
- failed to get federation peer: %w
- failed to list federation peers: %w
- failed to get comments: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/0fb71abe8fc84660.
Report an issue: GitHub.