gastownhall/beads · error
get next child ID: update counter: %w
Error message
get next child ID: update counter: %w
What it means
GetNextChildIDTx persists the incremented counter via INSERT ... ON DUPLICATE KEY UPDATE on the counter table; any failure is wrapped as "get next child ID: update counter: %w". If this fails, the child ID was computed but not created and the transaction should roll back. Called from ExecuteCreate.
Source
Thrown at internal/storage/issueops/child_id.go:58
return "", fmt.Errorf("get next child ID: scan child row: %w", err)
}
_, childNum, ok := ParseHierarchicalID(id)
if ok && childNum > lastChild {
lastChild = childNum
}
}
if err := rows.Err(); err != nil {
return "", fmt.Errorf("get next child ID: iterate children: %w", err)
}
nextChild := lastChild + 1
//nolint:gosec // G201: counterTable is one of two hardcoded constants.
if _, err := tx.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s (parent_id, last_child) VALUES (?, ?)
ON DUPLICATE KEY UPDATE last_child = ?
`, counterTable), parentID, nextChild, nextChild); err != nil {
return "", fmt.Errorf("get next child ID: update counter: %w", err)
}
return fmt.Sprintf("%s.%d", parentID, nextChild), nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Retry the create transaction; the ON DUPLICATE KEY UPDATE is idempotent so a retry recomputes the correct next child.
- Check wrapped error for 'lock wait timeout' or 'deadlock' and serialize creation under the same parent.
- Run schema migrations so the counter table exists.
- Keep the parent-create transaction short to reduce counter-row contention.
Example fix
// before
id, err := issueops.GetNextChildIDTx(ctx, tx, parentID)
if err != nil { return err } // dead transaction reused later
// after
id, err := issueops.GetNextChildIDTx(ctx, tx, parentID)
if err != nil {
tx.Rollback()
return fmt.Errorf("create child: %w", err) // retry whole tx
} Defensive patterns
Strategy: retry
Validate before calling
if ctx.Err() != nil { return ctx.Err() } Try / catch
id, err := issueops.GetNextChildIDTx(ctx, tx, parentID)
if err != nil {
tx.Rollback()
return retry(fmt.Errorf("counter update failed: %w", err)) // safe: idempotent recompute
} Prevention
- Serialize sub-issue creation under the same parent (or accept retries).
- Keep transactions short to avoid counter-row lock waits.
- Ensure schema migrations create the counter table before use.
- Treat this error as retryable: recomputing the child ID is deterministic.
When it happens
Trigger: Updating the counter when: the counter table is missing, a lock-wait/deadlock occurs with a concurrent creator of the same parent, the transaction is aborted, or the connection drops.
Common situations: Two agents concurrently creating sub-issues under the same parent (lock contention on the counter row); unmigrated schema; long transactions hitting innodb_lock_wait_timeout.
Related errors
- failed to increment issue counter for prefix %q: %w
- failed to increment issue counter after seeding for prefix %
- failed to read issue counter after increment for prefix %q:
- db: ChildCounterSQLRepository.NextChildID: read counter for
- get next child ID: read counter: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/185f23b8aa510124.
Report an issue: GitHub.