gastownhall/beads · error
db: ChildCounterSQLRepository.NextChildID: upsert counter fo
Error message
db: ChildCounterSQLRepository.NextChildID: upsert counter for %s: %w
What it means
NextChildID persists the computed next child number via an INSERT ... ON DUPLICATE KEY UPDATE into `child_counters` (or `wisp_child_counters`). This error wraps any ExecContext failure from that upsert. It means the child ID was computed but could not be recorded, so callers must not use the returned child ID.
Source
Thrown at internal/storage/domain/db/child_counter.go:81
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(`
INSERT INTO %s (parent_id, last_child) VALUES (?, ?)
ON DUPLICATE KEY UPDATE last_child = ?
`, counterTable), parentID, next, next); err != nil {
return "", fmt.Errorf("db: ChildCounterSQLRepository.NextChildID: upsert counter for %s: %w", parentID, err)
}
return fmt.Sprintf("%s.%d", parentID, next), nil
}
func (r *childCounterSQLRepositoryImpl) parentIsActiveWisp(ctx context.Context, parentID string) (bool, error) {
var probe int
err := r.runner.QueryRowContext(ctx, "SELECT 1 FROM wisps WHERE id = ? LIMIT 1", parentID).Scan(&probe)
switch {
case err == nil:
return true, nil
case errors.Is(err, sql.ErrNoRows):
return false, nil
case dberrors.IsTableNotExist(err):
return false, nil
default:
return false, err
}View on GitHub (pinned to 71377f2769)
Solutions
- Run schema migrations to ensure `child_counters` and `wisp_child_counters` tables exist (check dberrors.IsTableNotExist on the wrapped error).
- Retry under contention; serialize concurrent child-creation for the same parent (lock or retry with backoff).
- Verify the connection is writable (not a read-only replica) and the server has disk space.
- Check the wrapped error for lock-wait-timeout and tune innodb_lock_wait_timeout or reduce transaction scope.
Example fix
// before: counter table missing after manual DB setup
// ERROR: Table 'beads.child_counters' doesn't exist
// after: ensure migrations ran before repository use
if err := db.Migrate(conn); err != nil { log.Fatal(err) } Defensive patterns
Strategy: validation
Validate before calling
var exists int
if err := conn.QueryRowContext(ctx,
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'child_counters'").Scan(&exists); err != nil || exists == 0 {
return fmt.Errorf("child_counters table missing; run migrations")
} Type guard
func isUpsertError(err error) bool {
return err != nil && strings.Contains(err.Error(), "NextChildID: upsert counter")
} Try / catch
id, err := repo.NextChildID(ctx, parentID, opts)
if err != nil && isUpsertError(err) {
if dberrors.IsTableNotExist(err) { migrate(); return repo.NextChildID(ctx, parentID, opts) }
return err // lock contention / read-only: surface to caller
} Prevention
- Run schema migrations at startup
- Serialize concurrent child creation per parent
- Confirm the connection isn't read-only before writes
When it happens
Trigger: Calling NextChildID when the counter table is missing (schema not migrated), the write is rejected (read-only replica, disk full, lock wait timeout), or the context is canceled during the write.
Common situations: Running against a database missing the child_counters/wisp_child_counters migration; duplicate-key/lock contention under concurrent child creation; read-only connection used for a write.
Related errors
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
- failed to query orphaned dependencies: %w
- row iteration error: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/244f225ac79ce9d1.
Report an issue: GitHub.