{"record":{"id":"3222a3b904b9f378","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-countsbyissueids-in","errorCode":null,"errorMessage":"db: DependencySQLRepository.CountsByIssueIDs (in): %w","messagePattern":"db: DependencySQLRepository\\.CountsByIssueIDs \\(in\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":476,"sourceCode":"\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),\n\t\tParent:    make(map[string]string),\n\t}\n\tif len(issueIDs) == 0 {\n\t\treturn info, nil\n\t}\n\n\ttable := pickDepTable(opts.UseWispsTable)\n\tidPlaceholders, idArgs := buildInPlaceholders(issueIDs)\n","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L458-L494","documentation":"Wraps a failure from the incoming 'blocks' count query in CountsByIssueIDs (COUNT(*) grouped by depends_on_id for the DependentCount field). The outgoing count succeeded but the inbound aggregate failed, so the whole counts map is discarded (returns nil, error).","triggerScenarios":"CountsByIssueIDs(ctx, issueIDs, opts): the inbound aggregate SELECT fails or scanCounts errors — connection drop between the two queries, placeholder limit, wisps-table mismatch, or type-scan mismatch.","commonSituations":"Same bulk-count contexts as (out), plus transient mid-batch connection loss where the first query succeeds and the second fails.","solutions":["Retry the whole call; counts are cheap read-only aggregates and both halves must succeed for a consistent map.","Chunk issueIDs to avoid placeholder limits.","Verify UseWispsTable matches the deps table.","Check connection stability/pool settings if only the second query keeps failing."],"exampleFix":"// before\ncounts, err := deps.CountsByIssueIDs(ctx, ids, opts)\nif err != nil { return nil, err } // may be the (in) half failing transiently\n// after\ncounts, err := retry.Do(ctx, 3, backoff, func() (map[string]*types.DependencyCounts, error) {\n    return deps.CountsByIssueIDs(ctx, ids, opts)\n})","handlingStrategy":"retry","validationCode":"if len(issueIDs) == 0 { return map[string]*types.DependencyCounts{}, nil }\nif len(issueIDs) > 500 { /* chunk before calling */ }","typeGuard":null,"tryCatchPattern":"counts, err := deps.CountsByIssueIDs(ctx, ids, opts)\nif err != nil {\n    if isTransientDBError(err) {\n        counts, err = deps.CountsByIssueIDs(ctx, ids, opts) // retry whole call for consistency\n    }\n}","preventionTips":["Retry the full call, not one half, so DependencyCount and DependentCount stay consistent.","Chunk IDs to avoid placeholder limits.","Keep connection pool healthy; mid-call drops trigger this on the second query.","Verify the wisps-table flag before bulk counts."],"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"}