{"record":{"id":"192dc4cf774635b6","repo":"gastownhall/beads","slug":"db-eventssqlrepository-countallforids-w","errorCode":null,"errorMessage":"db: EventsSQLRepository.CountAllForIDs: %w","messagePattern":"db: EventsSQLRepository\\.CountAllForIDs: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/events.go","lineNumber":87,"sourceCode":"\t\ttotal += int(n)\n\t}\n\treturn total, nil\n}\n\nfunc (r *eventsSQLRepositoryImpl) CountAllForIDs(ctx context.Context, ids []string, opts domain.RecordEventOpts) (int, error) {\n\tif len(ids) == 0 {\n\t\treturn 0, nil\n\t}\n\ttable := \"events\"\n\tif opts.UseWispsTable {\n\t\ttable = \"wisp_events\"\n\t}\n\tcount, err := issueops.CountRowsForIssueIDsInTx(ctx, r.runner, table, ids)\n\tif err != nil {\n\t\tif opts.UseWispsTable && dberrors.IsTableNotExist(err) {\n\t\t\treturn 0, nil\n\t\t}\n\t\treturn 0, fmt.Errorf(\"db: EventsSQLRepository.CountAllForIDs: %w\", err)\n\t}\n\treturn count, nil\n}\n","sourceCodeStart":69,"sourceCodeEnd":91,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/events.go#L69-L91","documentation":"Wraps failures from CountRowsForIssueIDsInTx when counting event rows for issue IDs in events or wisp_events. Like other wisp paths, a missing wisp table returns 0,nil, so this error indicates a real count failure on an existing table.","triggerScenarios":"Calling CountAllForIDs when the SELECT COUNT fails: bad table name from opts, connection error, context cancellation, or malformed ID list causing SQL errors.","commonSituations":"Context deadline exceeded during a large count; events table locked by heavy write traffic; typo or drift in table configuration leading to a non-wisp missing table.","solutions":["Inspect the wrapped driver error (context deadline, lock timeout, unknown table)","Run migrations if the non-wisp events table is missing","Increase the context timeout or scope IDs to smaller batches","Check for long-running transactions blocking the count"],"exampleFix":"// before\ncount, err := repo.CountAllForIDs(ctx, ids, opts) // ctx has 1s timeout\n// after\nctx, cancel := context.WithTimeout(ctx, 30*time.Second)\ndefer cancel()\ncount, err := repo.CountAllForIDs(ctx, ids, opts)","handlingStrategy":"try-catch","validationCode":"if len(ids) == 0 { return 0, nil }\nctx, cancel := context.WithTimeout(ctx, 30*time.Second)\ndefer cancel()","typeGuard":"func isTransientCountErr(err error) bool {\n    return errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || isLockTimeoutErr(err)\n}","tryCatchPattern":"count, err := repo.CountAllForIDs(ctx, ids, opts)\nif err != nil {\n    if isTransientCountErr(err) { return retryCount(ctx, ids, opts) }\n    return fmt.Errorf(\"count events: %w\", err)\n}","preventionTips":["Use adequate context timeouts for counts over large tables","Run migrations so the events table exists","Retry transient lock/timeouts with backoff"],"tags":["database","events","count"],"backgroundTag":"db-count-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}