{"record":{"id":"7491543a558dd1e3","repo":"gastownhall/beads","slug":"db-childcountersqlrepository-nextchildid-read-co","errorCode":null,"errorMessage":"db: ChildCounterSQLRepository.NextChildID: read counter for %s: %w","messagePattern":"db: ChildCounterSQLRepository\\.NextChildID: read counter for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/child_counter.go","lineNumber":50,"sourceCode":"\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}\n\n\trows, err := r.runner.QueryContext(ctx, fmt.Sprintf(`\n\t\tSELECT id FROM %s\n\t\tWHERE id LIKE CONCAT(?, '.%%')\n\t\t  AND id NOT LIKE CONCAT(?, '.%%.%%')\n\t`, issueTable), parentID, parentID) //nolint:gosec // G201: issueTable is one of two hardcoded constants\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: scan existing children of %s: %w\", parentID, err)\n\t}\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar id string\n\t\tif err := rows.Scan(&id); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: scan: %w\", err)\n\t\t}\n\t\tif n, ok := parseChildSuffix(id); ok && n > lastChild {\n\t\t\tlastChild = n","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/child_counter.go#L32-L68","documentation":"Wrapping error from ChildCounterSQLRepository.NextChildID at internal/storage/domain/db/child_counter.go:50. After resolving the counter table (child_counters or wisp_child_counters), the code reads the last used child number with a scalar query. A missing row is handled (lastChild = 0); any other scan/query error is wrapped with this message, meaning the counter row exists but could not be read (or the query itself failed).","triggerScenarios":"Calling NextChildID when the SELECT of the counter row for parentID fails with an error other than sql.ErrNoRows — lock contention on the counter row, SQL syntax/type error, dropped connection.","commonSituations":"Two processes contending for the same parent's counter row under heavy concurrency; counter table corrupted; migration left the counter table in an incompatible state.","solutions":["Unwrap the chain to see the exact SQL error","Check for row locks / concurrent writers on the counter table and serialize writes","Verify the counter table schema matches the running bd version","Retry the operation after resolving contention or corruption"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// confirm the parent's counter row is readable before generating children\nvar last int\nerr := store.QueryRowContext(ctx,\n\t\"SELECT last_child FROM child_counters WHERE parent = ?\", parentID).Scan(&last)\nif err != nil && !errors.Is(err, sql.ErrNoRows) {\n\treturn fmt.Errorf(\"counter row unreadable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"id, err := repo.NextChildID(ctx, parentID)\nif err != nil && strings.Contains(err.Error(), \"read counter\") {\n\tif isLockContention(errors.Unwrap(err)) {\n\t\ttime.Sleep(250 * time.Millisecond) // let the competing writer finish\n\t\tid, err = repo.NextChildID(ctx, parentID)\n\t}\n}\nreturn id, err","preventionTips":["Serialize child-ID generation per parent (single writer or row lock)","Watch for concurrent processes creating children of the same parent","Verify counter-table schema after migrations","Back off and retry on lock-wait timeouts"],"tags":["database","child-id","counter","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"}