{"record":{"id":"2abb5d1d2e7c1174","repo":"gastownhall/beads","slug":"get-next-child-id-read-counter-w","errorCode":null,"errorMessage":"get next child ID: read counter: %w","messagePattern":"get next child ID: read counter: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/child_id.go","lineNumber":23,"sourceCode":"\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetNextChildIDTx(ctx context.Context, tx *sql.Tx, parentID string) (string, error) {\n\tcounterTable, issueTable := \"child_counters\", \"issues\"\n\tif IsActiveWispInTx(ctx, tx, parentID) {\n\t\tcounterTable, issueTable = \"wisp_child_counters\", \"wisps\"\n\t}\n\n\tvar lastChild int\n\t//nolint:gosec // G201: counterTable is one of two hardcoded constants.\n\terr := tx.QueryRowContext(ctx,\n\t\tfmt.Sprintf(\"SELECT last_child FROM %s WHERE parent_id = ?\", counterTable),\n\t\tparentID).Scan(&lastChild)\n\tif err == sql.ErrNoRows {\n\t\tlastChild = 0\n\t} else if err != nil {\n\t\treturn \"\", fmt.Errorf(\"get next child ID: read counter: %w\", err)\n\t}\n\n\t//nolint:gosec // G201: issueTable is one of two hardcoded constants.\n\trows, err := tx.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)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"get next child ID: query existing children: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar id string\n\t\tif err := rows.Scan(&id); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"get next child ID: scan child row: %w\", err)\n\t\t}","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/child_id.go#L5-L41","documentation":"GetNextChildIDTx reads the child-ID counter row for a parent from the counter table (SELECT last_child WHERE parent_id = ?) and wraps any Scan error other than ErrNoRows with \"get next child ID: read counter: %w\". ErrNoRows is treated as 'no children yet' (counter 0), so this error means the counter lookup failed for a real reason. Called from ExecuteCreate when generating hierarchical IDs like bd-12.3.","triggerScenarios":"Calling ExecuteCreate/GetNextChildIDTx when: the counter table is missing or corrupted, the parent_id argument is malformed/NULL causing a driver error, the transaction is dead, or the connection drops during the row read.","commonSituations":"Creating sub-issues against an unmigrated database (counter table absent); a corrupted counter row with a non-integer last_child that fails to scan into int; connection reset while creating children in bulk.","solutions":["Migrate the database so the child counter table exists.","Check the wrapped error text: 'no such table' -> migrate; conversion/scan type error -> inspect the counter row's last_child value.","Retry the create transaction if the failure was a transient connection error.","Verify the parentID is a valid existing issue ID before creating children."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"parent, err := store.GetIssue(ctx, parentID)\nif err != nil || parent == nil { return fmt.Errorf(\"parent %s does not exist\", parentID) }","typeGuard":null,"tryCatchPattern":"id, err := issueops.GetNextChildIDTx(ctx, tx, parentID)\nif err != nil {\n    tx.Rollback()\n    return retryable(fmt.Errorf(\"child id generation failed: %w\", err))\n}","preventionTips":["Verify the parent issue exists before creating children.","Ensure schema is migrated (counter table present) after upgrades.","Retry transient failures; child ID generation is transactional and safe to redo.","Do not hand-edit counter rows in the database."],"tags":["database","sql","child-ids","counter"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}