{"record":{"id":"9290b7b9d73cded4","repo":"gastownhall/beads","slug":"db-childcountersqlrepository-nextchildid-probe-p","errorCode":null,"errorMessage":"db: ChildCounterSQLRepository.NextChildID: probe parent table for %s: %w","messagePattern":"db: ChildCounterSQLRepository\\.NextChildID: probe parent table for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/child_counter.go","lineNumber":33,"sourceCode":"func NewChildCounterSQLRepository(runner Runner) domain.ChildCounterSQLRepository {\n\treturn &childCounterSQLRepositoryImpl{runner: runner}\n}\n\ntype childCounterSQLRepositoryImpl struct {\n\trunner Runner\n}\n\nvar _ domain.ChildCounterSQLRepository = (*childCounterSQLRepositoryImpl)(nil)\n\nfunc (r *childCounterSQLRepositoryImpl) NextChildID(ctx context.Context, parentID string, _ domain.ChildCounterOpts) (string, error) {\n\tif parentID == \"\" {\n\t\treturn \"\", errors.New(\"db: ChildCounterSQLRepository.NextChildID: parentID must not be empty\")\n\t}\n\n\tcounterTable, issueTable := \"child_counters\", \"issues\"\n\tparentIsWisp, err := r.parentIsActiveWisp(ctx, parentID)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: probe parent table for %s: %w\", parentID, err)\n\t}\n\tif parentIsWisp {\n\t\tcounterTable, issueTable = \"wisp_child_counters\", \"wisps\"\n\t}\n\n\tvar lastChild int\n\terr = r.runner.QueryRowContext(ctx,\n\t\t//nolint:gosec // G201: counterTable is one of two hardcoded constants\n\t\tfmt.Sprintf(\"SELECT last_child FROM %s WHERE parent_id = ?\", counterTable),\n\t\tparentID,\n\t).Scan(&lastChild)\n\tswitch {\n\tcase err == nil:\n\tcase errors.Is(err, sql.ErrNoRows):\n\t\tlastChild = 0\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: read counter for %s: %w\", parentID, err)\n\t}","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/child_counter.go#L15-L51","documentation":"Wrapping error from ChildCounterSQLRepository.NextChildID at internal/storage/domain/db/child_counter.go:33. Before reading the counter, NextChildID probes which table the parent lives in (issues vs wisps) via r.parentIsActiveWisp; if that probe query fails, the child-ID generation aborts with this error. The %w chain preserves the SQL-level cause; parentID is interpolated into the message.","triggerScenarios":"Calling NextChildID(ctx, parentID) with a non-empty parentID whose table probe (parentIsActiveWisp) returns a DB error — SQL failure, connection loss, cancelled context. (An empty parentID produces a different, non-wrapped error on the preceding line.)","commonSituations":"Parent issue ID referencing a row in a corrupted DB; connection pool exhausted; Dolt server restarted mid-command while generating child IDs in bulk.","solutions":["Unwrap the chain to see the SQL error from the parent-table probe","Verify the parent ID exists and the issues/wisps tables are readable","Reconnect to the database and retry","If bulk-generating children, batch with retries around transient connection errors"],"exampleFix":"// before\nparentIsWisp, err := r.parentIsActiveWisp(ctx, parentID)\nif err != nil {\n\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: probe parent table for %s: %w\", parentID, err)\n}\n// after\nparentIsWisp, err := r.parentIsActiveWisp(ctx, parentID)\nif err != nil {\n\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: probe parent table for %s: %w\", parentID, err) // caller retries on transient errors\n}","handlingStrategy":"retry","validationCode":"// validate inputs and storage before generating child IDs\nif parentID == \"\" {\n\treturn errors.New(\"parentID must not be empty\")\n}\nif err := pingStore(ctx); err != nil {\n\treturn fmt.Errorf(\"cannot generate child id, storage unreachable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"id, err := repo.NextChildID(ctx, parentID)\nif err != nil && strings.Contains(err.Error(), \"probe parent table\") {\n\tif isTransient(errors.Unwrap(err)) { // connection refused, deadline, lock timeout\n\t\tbackoff := 100 * time.Millisecond\n\t\tfor i := 0; i < 3; i++ {\n\t\t\ttime.Sleep(backoff)\n\t\t\tbackoff *= 2\n\t\t\tif id, err = repo.NextChildID(ctx, parentID); err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\nreturn id, err","preventionTips":["Always check the parent ID exists before generating children","Retry transient SQL errors with exponential backoff","Keep connection pools healthy (set max open/idle limits)","Run storage integrity checks if probe errors recur"],"tags":["database","child-id","sql","error-wrapping"],"backgroundTag":"child-id-generation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}