gastownhall/beads · error

failed to batch-fetch child statuses from %s: %w

Error message

failed to batch-fetch child statuses from %s: %w

What it means

To decide closure eligibility, the function batch-fetches child issue statuses with `SELECT id, status FROM <table> WHERE id IN (...)`. A query failure on a table that exists (the not-exist case is tolerated for wisps) is wrapped with this message and the table name. It means child statuses could not be loaded, so epics cannot be safely evaluated for closure.

Source

Thrown at internal/storage/issueops/epic_closure.go:92

	childStatusMap := make(map[string]string)
	if len(allChildIDs) > 0 {
		// Check both issues and wisps tables for child statuses (bd-w2w)
		for _, table := range []string{"issues", "wisps"} {
			for start := 0; start < len(allChildIDs); start += queryBatchSize {
				end := start + queryBatchSize
				if end > len(allChildIDs) {
					end = len(allChildIDs)
				}
				batch := allChildIDs[start:end]
				placeholders, args := buildSQLInClause(batch)

				statusQuery := fmt.Sprintf("SELECT id, status FROM %s WHERE id IN (%s)", table, placeholders)
				statusRows, err := tx.QueryContext(ctx, statusQuery, args...)
				if err != nil {
					if isTableNotExistError(err) {
						break // wisps table may not exist on pre-migration databases
					}
					return nil, fmt.Errorf("failed to batch-fetch child statuses from %s: %w", table, err)
				}
				for statusRows.Next() {
					var id, status string
					if err := statusRows.Scan(&id, &status); err != nil {
						statusRows.Close()
						return nil, fmt.Errorf("scan child status: %w", err)
					}
					childStatusMap[id] = status
				}
				statusRows.Close()
			}
		}
	}

	// Step 4: Batch-fetch all epic issues
	epicsWithChildren := make([]string, 0)
	for _, epicID := range epicIDs {
		if len(epicChildMap[epicID]) > 0 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the table name and wrapped cause; verify the table has `id` and `status` columns.
  2. Retry — transient connection errors usually clear.
  3. If parameter limits are the cause, split work into smaller batches or chunk epics.
  4. Run migrations to fix schema drift.
Defensive patterns

Strategy: retry

Validate before calling

if len(childIDs) > 500 { return errors.New("child set too large; chunk before closure analysis") }
if err := db.PingContext(ctx); err != nil { return err }

Type guard

func isStatusFetchErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to batch-fetch child statuses from")
}

Try / catch

epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)
if err != nil {
    if isStatusFetchErr(err) && errors.Is(err, driver.ErrBadConn) {
        return retry(ctx, epics)
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetEpicsEligibleForClosureInTx when the batched status query errors — too many placeholders/args overflow on huge child sets, connection failure, or missing status column.

Common situations: Very large epics producing SQL statements that exceed driver parameter limits; Dolt server restarts mid-command; schema drift removing the status column.

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


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