gastownhall/beads · error

db: DependencySQLRepository.CountsByIssueIDs (out): %w

Error message

db: DependencySQLRepository.CountsByIssueIDs (out): %w

What it means

Wraps a failure from the outgoing 'blocks' count query in CountsByIssueIDs (SELECT issue_id, COUNT(*) ... type='blocks' GROUP BY issue_id) executed via scanCounts. The per-issue outgoing blocking count could not be fetched, so the counts map is not returned at all.

Source

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

func (r *dependencySQLRepositoryImpl) CountsByIssueIDs(ctx context.Context, issueIDs []string, opts domain.DepCountsOpts) (map[string]*types.DependencyCounts, error) {
	result := make(map[string]*types.DependencyCounts)
	if len(issueIDs) == 0 {
		return result, nil
	}
	for _, id := range issueIDs {
		result[id] = &types.DependencyCounts{}
	}

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

	//nolint:gosec // G201: table is one of two hardcoded constants
	outQ := fmt.Sprintf(
		`SELECT issue_id, COUNT(*) FROM %s WHERE issue_id IN (%s) AND type = 'blocks' GROUP BY issue_id`,
		table, idPlaceholders,
	)
	if err := scanCounts(ctx, r.runner, outQ, idArgs, result, func(c *types.DependencyCounts, n int) { c.DependencyCount = n }); err != nil {
		return nil, fmt.Errorf("db: DependencySQLRepository.CountsByIssueIDs (out): %w", err)
	}

	//nolint:gosec // G201: table and depTargetExpr are hardcoded
	inQ := fmt.Sprintf(
		`SELECT %s AS depends_on_id, COUNT(*) FROM %s WHERE %s IN (%s) AND type = 'blocks' GROUP BY %s`,
		depTargetExpr, table, depTargetExpr, idPlaceholders, depTargetExpr,
	)
	if err := scanCounts(ctx, r.runner, inQ, idArgs, result, func(c *types.DependencyCounts, n int) { c.DependentCount = n }); err != nil {
		return nil, fmt.Errorf("db: DependencySQLRepository.CountsByIssueIDs (in): %w", err)
	}

	return result, nil
}

func (r *dependencySQLRepositoryImpl) GetBlockingInfo(ctx context.Context, issueIDs []string, opts domain.DepListOpts) (domain.BlockingInfo, error) {
	info := domain.BlockingInfo{
		BlockedBy: make(map[string][]string),
		Blocks:    make(map[string][]string),

View on GitHub (pinned to 71377f2769)

Solutions

  1. Chunk issueIDs into batches under the driver placeholder limit.
  2. Verify opts.UseWispsTable matches the storage table.
  3. Retry; the queries are read-only aggregates.
  4. Check DB load/timeouts and add appropriate context timeout.

Example fix

// before
counts, err := deps.CountsByIssueIDs(ctx, manyIDs, domain.DepCountsOpts{})
// after
merged := map[string]*types.DependencyCounts{}
for batch := range slices.Chunk(manyIDs, 500) {
    c, err := deps.CountsByIssueIDs(ctx, batch, domain.DepCountsOpts{})
    if err != nil { return err }
    for k, v := range c { merged[k] = v }
}
Defensive patterns

Strategy: validation

Validate before calling

if len(issueIDs) == 0 { return map[string]*types.DependencyCounts{}, nil }
if len(issueIDs) > 500 { /* chunk before calling */ }

Try / catch

if err != nil && strings.Contains(err.Error(), "CountsByIssueIDs (out)") {
    // retry read-only aggregate or chunk the ID list
}

Prevention

When it happens

Trigger: CountsByIssueIDs(ctx, issueIDs, opts) with the outbound aggregate query failing: placeholder limit overflow with large ID lists, connection error, wrong table via UseWispsTable, or scan error in scanCounts.

Common situations: Board/bulk views counting deps for hundreds of issues at once; wisps vs issues table mismatch; DB under load timing out.

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