gastownhall/beads · error

db: DependencySQLRepository.GetBlockingInfo: outbound: %w

Error message

db: DependencySQLRepository.GetBlockingInfo: outbound: %w

What it means

Wraps a failure from the outbound scanBlockingRows query in GetBlockingInfo, which selects outgoing 'blocks' and 'parent-child' edges for the given issues. The BlockingInfo result (Blocks/BlockedBy/Parent maps) cannot be built, so the caller gets an empty struct and this error.

Source

Thrown at internal/storage/domain/db/dependency.go:502

		BlockedBy: make(map[string][]string),
		Blocks:    make(map[string][]string),
		Parent:    make(map[string]string),
	}
	if len(issueIDs) == 0 {
		return info, nil
	}

	table := pickDepTable(opts.UseWispsTable)
	idPlaceholders, idArgs := buildInPlaceholders(issueIDs)

	//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{}{}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Chunk issueIDs and merge the BlockingInfo results.
  2. Verify opts.UseWispsTable matches where the deps live.
  3. Retry on a fresh connection; read-only.
  4. Confirm dep table schema matches expectations after upgrades (scan errors).

Example fix

// before
info, err := deps.GetBlockingInfo(ctx, ids, domain.DepListOpts{UseWispsTable: false})
// after
info, err := deps.GetBlockingInfo(ctx, ids[:500], domain.DepListOpts{UseWispsTable: false})
if err != nil { return err }
info2, err := deps.GetBlockingInfo(ctx, ids[500:], domain.DepListOpts{UseWispsTable: false})
mergeBlockingInfo(info, info2)
Defensive patterns

Strategy: validation

Validate before calling

if len(issueIDs) == 0 { return domain.BlockingInfo{}, nil }
if len(issueIDs) > 500 { /* chunk and merge BlockingInfo maps */ }

Try / catch

if err != nil && strings.Contains(err.Error(), "GetBlockingInfo: outbound") {
    // retry read-only or chunk IDs and merge Blocks/Parent maps
}

Prevention

When it happens

Trigger: GetBlockingInfo(ctx, issueIDs, opts) with Direction-dependent outbound query failing: placeholder limit for many IDs, connection error, table mismatch (UseWispsTable), or scan error in scanBlockingRows.

Common situations: Rendering 'blocked by' panels for many issues; querying wisps with the wrong flag; long-running sessions whose connection was reaped by the server.

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/193cf954aa09ae2c. Report an issue: GitHub.