gastownhall/beads · error

scan child status: %w

Error message

scan child status: %w

What it means

Rows from the child-status batch query are scanned into two strings (id, status). A Scan mismatch aborts with this error. It means a child row's id or status could not be read as a string, so the child-status map would be incomplete and closure evaluation is halted.

Source

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

				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 {
			epicsWithChildren = append(epicsWithChildren, epicID)
		}
	}
	epicIssues, err := GetIssuesByIDsInTx(ctx, tx, epicsWithChildren, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to batch-fetch epic issues: %w", err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Locate rows with NULL/invalid status in the named table and set them to a valid status.
  2. Align the schema with the current bd migrations.
  3. Remove corrupt rows and re-sync from the primary database.
  4. Retry the closure command after repair.

Example fix

// before
UPDATE issues SET status = NULL WHERE id = 'bd-1';
// after
UPDATE issues SET status = 'open' WHERE id = 'bd-1';
Defensive patterns

Strategy: validation

Validate before calling

rows, _ := db.Query("SELECT id, status FROM issues WHERE id IN (?)", ids)
for rows.Next() {
    var id, status sql.NullString
    _ = rows.Scan(&id, &status)
    if !id.Valid || !status.Valid { return fmt.Errorf("child %v has NULL status", id) }
}

Type guard

func isChildStatusScanErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "scan child status:")
}

Try / catch

epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)
if err != nil {
    if isChildStatusScanErr(err) { return backfillStatusesThenRetry(ctx) }
    return err
}

Prevention

When it happens

Trigger: Calling GetEpicsEligibleForClosureInTx when a status row contains a NULL or non-string id/status value that the driver cannot convert.

Common situations: Rows with NULL status inserted by other tooling; enum/typed status columns in a divergent schema; corrupted wisps/issues rows.

Related errors


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