gastownhall/beads · error

db: ChildCounterSQLRepository.NextChildID: rows: %w

Error message

db: ChildCounterSQLRepository.NextChildID: rows: %w

What it means

After iterating child IDs, NextChildID calls rows.Err() to surface any error that occurred during iteration (driver I/O, query canceled, connection lost). This error wraps that post-iteration check. It means the result stream from the child-id query failed partway, so the computed `lastChild` is unreliable and no child ID was produced.

Source

Thrown at internal/storage/domain/db/child_counter.go:72

		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(`
		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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped driver error (errors.Unwrap) — context.DeadlineExceeded means increase timeout or optimize the child query.
  2. Retry the operation; the upsert only happens after this check so a retry is safe.
  3. Verify network stability between the app and the Dolt/MySQL server (timeouts, proxies).
  4. If cancelations are intentional, use a longer context for NextChildID during bulk operations.

Example fix

// before: bare context cancelled mid-iteration
ctx := context.Background()

// after: give child-scan queries an explicit generous deadline
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
Defensive patterns

Strategy: retry

Validate before calling

if err := ctx.Err(); err != nil {
    return fmt.Errorf("context already done before NextChildID: %w", err)
}

Type guard

func isIterationError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "NextChildID: rows:")
}

Try / catch

for attempt := 0; attempt < 3; attempt++ {
    id, err := repo.NextChildID(ctx, parentID, opts)
    if err == nil { return id, nil }
    if !isIterationError(err) || ctx.Err() != nil { return "", err }
    time.Sleep(backoff(attempt))
}

Prevention

When it happens

Trigger: Calling NextChildID while the underlying connection breaks during rows iteration, the context is canceled (Ctrl-C, deadline exceeded), or the server kills the query mid-stream.

Common situations: Long-running scans over large issue tables hitting network timeouts; context deadline too short for big parents; server-side wait_timeout dropping idle connections.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/142bd25712a5e50b. Report an issue: GitHub.