{"record":{"id":"a7eadc627c9df55d","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-countsbyissueids-out","errorCode":null,"errorMessage":"db: DependencySQLRepository.CountsByIssueIDs (out): %w","messagePattern":"db: DependencySQLRepository\\.CountsByIssueIDs \\(out\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":467,"sourceCode":"func (r *dependencySQLRepositoryImpl) CountsByIssueIDs(ctx context.Context, issueIDs []string, opts domain.DepCountsOpts) (map[string]*types.DependencyCounts, error) {\n\tresult := make(map[string]*types.DependencyCounts)\n\tif len(issueIDs) == 0 {\n\t\treturn result, nil\n\t}\n\tfor _, id := range issueIDs {\n\t\tresult[id] = &types.DependencyCounts{}\n\t}\n\n\tidPlaceholders, idArgs := buildInPlaceholders(issueIDs)\n\ttable := pickDepTable(opts.UseWispsTable)\n\n\t//nolint:gosec // G201: table is one of two hardcoded constants\n\toutQ := fmt.Sprintf(\n\t\t`SELECT issue_id, COUNT(*) FROM %s WHERE issue_id IN (%s) AND type = 'blocks' GROUP BY issue_id`,\n\t\ttable, idPlaceholders,\n\t)\n\tif err := scanCounts(ctx, r.runner, outQ, idArgs, result, func(c *types.DependencyCounts, n int) { c.DependencyCount = n }); err != nil {\n\t\treturn nil, fmt.Errorf(\"db: DependencySQLRepository.CountsByIssueIDs (out): %w\", err)\n\t}\n\n\t//nolint:gosec // G201: table and depTargetExpr are hardcoded\n\tinQ := fmt.Sprintf(\n\t\t`SELECT %s AS depends_on_id, COUNT(*) FROM %s WHERE %s IN (%s) AND type = 'blocks' GROUP BY %s`,\n\t\tdepTargetExpr, table, depTargetExpr, idPlaceholders, depTargetExpr,\n\t)\n\tif err := scanCounts(ctx, r.runner, inQ, idArgs, result, func(c *types.DependencyCounts, n int) { c.DependentCount = n }); err != nil {\n\t\treturn nil, fmt.Errorf(\"db: DependencySQLRepository.CountsByIssueIDs (in): %w\", err)\n\t}\n\n\treturn result, nil\n}\n\nfunc (r *dependencySQLRepositoryImpl) GetBlockingInfo(ctx context.Context, issueIDs []string, opts domain.DepListOpts) (domain.BlockingInfo, error) {\n\tinfo := domain.BlockingInfo{\n\t\tBlockedBy: make(map[string][]string),\n\t\tBlocks:    make(map[string][]string),","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L449-L485","documentation":"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.","triggerScenarios":"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.","commonSituations":"Board/bulk views counting deps for hundreds of issues at once; wisps vs issues table mismatch; DB under load timing out.","solutions":["Chunk issueIDs into batches under the driver placeholder limit.","Verify opts.UseWispsTable matches the storage table.","Retry; the queries are read-only aggregates.","Check DB load/timeouts and add appropriate context timeout."],"exampleFix":"// before\ncounts, err := deps.CountsByIssueIDs(ctx, manyIDs, domain.DepCountsOpts{})\n// after\nmerged := map[string]*types.DependencyCounts{}\nfor batch := range slices.Chunk(manyIDs, 500) {\n    c, err := deps.CountsByIssueIDs(ctx, batch, domain.DepCountsOpts{})\n    if err != nil { return err }\n    for k, v := range c { merged[k] = v }\n}","handlingStrategy":"validation","validationCode":"if len(issueIDs) == 0 { return map[string]*types.DependencyCounts{}, nil }\nif len(issueIDs) > 500 { /* chunk before calling */ }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"CountsByIssueIDs (out)\") {\n    // retry read-only aggregate or chunk the ID list\n}","preventionTips":["Chunk ID batches for bulk count views.","Confirm UseWispsTable matches the deps table.","Set a sane context timeout for aggregate queries on large graphs.","Retry is safe; counts are read-only."],"tags":["database","query","aggregate"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}