{"record":{"id":"9565bf3e540dc240","repo":"gastownhall/beads","slug":"db-childcountersqlrepository-nextchildid-scan","errorCode":null,"errorMessage":"db: ChildCounterSQLRepository.NextChildID: scan: %w","messagePattern":"db: ChildCounterSQLRepository\\.NextChildID: scan: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/child_counter.go","lineNumber":65,"sourceCode":"\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\n\t\t}\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: rows: %w\", err)\n\t}\n\n\tnext := lastChild + 1\n\t//nolint:gosec // G201: counterTable is one of two hardcoded constants\n\tif _, err := r.runner.ExecContext(ctx, fmt.Sprintf(`\n\t\tINSERT INTO %s (parent_id, last_child) VALUES (?, ?)\n\t\tON DUPLICATE KEY UPDATE last_child = ?\n\t`, counterTable), parentID, next, next); err != nil {\n\t\treturn \"\", fmt.Errorf(\"db: ChildCounterSQLRepository.NextChildID: upsert counter for %s: %w\", parentID, err)\n\t}\n","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/child_counter.go#L47-L83","documentation":"NextChildID scans each child-issue ID returned by the query over `issues` (or `wisps`) to find the highest numeric suffix under the parent. This error wraps any database/sql rows.Scan failure while reading one ID column. It means a row could not be decoded into a Go string, which is a driver/row-decode failure rather than a missing-parent problem.","triggerScenarios":"Calling ChildCounterSQLRepository.NextChildID(ctx, parentID, opts) when an `id` value in the result set cannot be scanned into a string (e.g. NULL id in the table, driver type conversion failure, connection dropped mid-iteration).","commonSituations":"Corrupted or manually edited rows where `issues.id` is NULL; driver mismatch after switching Dolt/MySQL driver versions with stricter Scan conversion; network interruption while iterating rows.","solutions":["Check the `issues`/`wisps` table for rows with NULL or non-text `id` values and repair them (id is expected to be a NOT NULL text primary key).","Verify the database driver version matches what beads expects (Dolt-compatible driver); upgrade/downgrade the driver.","Retry the call; if transient connection errors appear, check server connectivity and timeouts.","Inspect the wrapped cause with errors.Unwrap to see the underlying driver error."],"exampleFix":"// before: rows contain NULL id, Scan into string fails\nvar id string\nrows.Scan(&id)\n\n// after: tolerate NULL ids defensively (or repair the schema to NOT NULL)\nvar id sql.NullString\nif err := rows.Scan(&id); err != nil { return \"\", err }\nif id.Valid { /* process id.String */ }","handlingStrategy":"try-catch","validationCode":"// Go has no pre-call validation for row decoding; ensure schema integrity instead:\n// SELECT COUNT(*) FROM issues WHERE id IS NULL  -- must be 0","typeGuard":"func isScanError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"ChildCounterSQLRepository.NextChildID: scan:\")\n}","tryCatchPattern":"childID, err := repo.NextChildID(ctx, parentID, opts)\nif err != nil {\n    if isScanError(err) {\n        // inspect/repair corrupted id row, then retry\n        return fmt.Errorf(\"corrupt child id row for %s: %w\", parentID, err)\n    }\n    return err\n}","preventionTips":["Keep `id` columns NOT NULL via schema constraints","Never hand-edit database rows; use bd commands","Pin the SQL driver version in go.mod"],"tags":["database","sql","scan","go"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}