gastownhall/beads · error
db: ChildCounterSQLRepository.NextChildID: scan existing chi
Error message
db: ChildCounterSQLRepository.NextChildID: scan existing children of %s: %w
What it means
Wrapping error from ChildCounterSQLRepository.NextChildID at internal/storage/domain/db/child_counter.go:59. After reading the counter, the code queries all existing direct children of the parent (LIKE '<parent>.%' and not deeper) to reconcile the counter with reality. A failure executing that query (QueryContext error) is wrapped with this message. Row-scan failures later in the loop produce a separate 'scan:' variant.
Source
Thrown at internal/storage/domain/db/child_counter.go:59
//nolint:gosec // G201: counterTable is one of two hardcoded constants
fmt.Sprintf("SELECT last_child FROM %s WHERE parent_id = ?", counterTable),
parentID,
).Scan(&lastChild)
switch {
case err == nil:
case errors.Is(err, sql.ErrNoRows):
lastChild = 0
default:
return "", fmt.Errorf("db: ChildCounterSQLRepository.NextChildID: read counter for %s: %w", parentID, err)
}
rows, err := r.runner.QueryContext(ctx, fmt.Sprintf(`
SELECT id FROM %s
WHERE id LIKE CONCAT(?, '.%%')
AND id NOT LIKE CONCAT(?, '.%%.%%')
`, issueTable), parentID, parentID) //nolint:gosec // G201: issueTable is one of two hardcoded constants
if err != nil {
return "", fmt.Errorf("db: ChildCounterSQLRepository.NextChildID: scan existing children of %s: %w", parentID, err)
}
defer rows.Close()
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return "", fmt.Errorf("db: ChildCounterSQLRepository.NextChildID: scan: %w", err)
}
if n, ok := parseChildSuffix(id); ok && n > lastChild {
lastChild = n
}
}
if err := rows.Err(); err != nil {
return "", fmt.Errorf("db: ChildCounterSQLRepository.NextChildID: rows: %w", err)
}
next := lastChild + 1
//nolint:gosec // G201: counterTable is one of two hardcoded constants
if _, err := r.runner.ExecContext(ctx, fmt.Sprintf(`View on GitHub (pinned to 71377f2769)
Solutions
- Unwrap to see the underlying SQL/driver error
- Check query timeouts if the parent has many children (index on id prefix)
- Verify connection stability between the counter read and children scan
- Retry once the database is reachable
Defensive patterns
Strategy: retry
Validate before calling
// ensure the children listing query runs before generating a new child ID
rows, err := store.QueryContext(ctx,
"SELECT id FROM issues WHERE id LIKE CONCAT(?, '.%') AND id NOT LIKE CONCAT(?, '.%.%')",
parentID, parentID)
if err != nil {
return fmt.Errorf("children of %s unlistable: %w", parentID, err)
}
rows.Close() Try / catch
id, err := repo.NextChildID(ctx, parentID)
if err != nil && strings.Contains(err.Error(), "scan existing children") {
if errors.Is(errors.Unwrap(err), context.DeadlineExceeded) {
longCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
id, err = repo.NextChildID(longCtx, parentID)
}
}
return id, err Prevention
- Index the id column so prefix scans over large parents stay fast
- Use a context deadline that tolerates many children under one parent
- Check connection stability for multi-query operations
- Retry transient query errors instead of failing the whole create flow
When it happens
Trigger: Calling NextChildID when the QueryContext listing direct children of parentID fails — connection dropped between the counter read and this query, SQL execution error, or the issue/wisp table is unreadable.
Common situations: Very large parent (many children) causing a slow query that hits a timeout; DB connection interrupted mid-operation; permissions issue on the issues/wisps table.
Related errors
- db: ChildCounterSQLRepository.NextChildID: probe parent tabl
- db: ChildCounterSQLRepository.NextChildID: read counter for
- failed to get federation peer: %w
- failed to list federation peers: %w
- iter dependents: query: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/12e96c80dbcb3f3e.
Report an issue: GitHub.