gastownhall/beads · error
get next child ID: read counter: %w
Error message
get next child ID: read counter: %w
What it means
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.
Source
Thrown at internal/storage/issueops/child_id.go:23
"database/sql"
"fmt"
)
func GetNextChildIDTx(ctx context.Context, tx *sql.Tx, parentID string) (string, error) {
counterTable, issueTable := "child_counters", "issues"
if IsActiveWispInTx(ctx, tx, parentID) {
counterTable, issueTable = "wisp_child_counters", "wisps"
}
var lastChild int
//nolint:gosec // G201: counterTable is one of two hardcoded constants.
err := tx.QueryRowContext(ctx,
fmt.Sprintf("SELECT last_child FROM %s WHERE parent_id = ?", counterTable),
parentID).Scan(&lastChild)
if err == sql.ErrNoRows {
lastChild = 0
} else if err != nil {
return "", fmt.Errorf("get next child ID: read counter: %w", err)
}
//nolint:gosec // G201: issueTable is one of two hardcoded constants.
rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
SELECT id FROM %s
WHERE id LIKE CONCAT(?, '.%%')
AND id NOT LIKE CONCAT(?, '.%%.%%')
`, issueTable), parentID, parentID)
if err != nil {
return "", fmt.Errorf("get next child ID: query existing children: %w", err)
}
defer rows.Close()
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return "", fmt.Errorf("get next child ID: scan child row: %w", err)
}View on GitHub (pinned to 71377f2769)
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.
Defensive patterns
Strategy: retry
Validate before calling
parent, err := store.GetIssue(ctx, parentID)
if err != nil || parent == nil { return fmt.Errorf("parent %s does not exist", parentID) } Try / catch
id, err := issueops.GetNextChildIDTx(ctx, tx, parentID)
if err != nil {
tx.Rollback()
return retryable(fmt.Errorf("child id generation failed: %w", err))
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- failed to increment issue counter for prefix %q: %w
- db: ChildCounterSQLRepository.NextChildID: read counter for
- get next child ID: query existing children: %w
- get next child ID: update counter: %w
- failed to increment issue counter for prefix %q: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2abb5d1d2e7c1174.
Report an issue: GitHub.