gastownhall/beads · error
scan parent-child dep from %s: %w
Error message
scan parent-child dep from %s: %w
What it means
Each dependency row is scanned into two strings (parentID, childID). A Scan failure aborts with the table name included. This means a row in the parent-child dependency result does not have the expected two-string shape, so dependency graph construction stops.
Source
Thrown at internal/storage/issueops/epic_closure.go:60
for _, id := range epicIDs {
epicSet[id] = true
}
for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
depRows, err := tx.QueryContext(ctx, fmt.Sprintf(`
SELECT %s AS parent_id, issue_id FROM %s
WHERE type = 'parent-child' AND %s IS NOT NULL
`, DepTargetExpr, depTable, DepTargetExpr))
if err != nil {
if optionalBlockedTable(depTable) && isTableNotExistError(err) {
continue // wisp_dependencies may not exist on pre-migration databases
}
return nil, fmt.Errorf("failed to get parent-child deps from %s: %w", depTable, err)
}
for depRows.Next() {
var parentID, childID string
if err := depRows.Scan(&parentID, &childID); err != nil {
depRows.Close()
return nil, fmt.Errorf("scan parent-child dep from %s: %w", depTable, err)
}
if epicSet[parentID] {
epicChildMap[parentID] = append(epicChildMap[parentID], childID)
}
}
depRows.Close()
}
// Step 3: Batch-fetch statuses for all child issues across all epics
allChildIDs := make([]string, 0)
for _, children := range epicChildMap {
allChildIDs = append(allChildIDs, children...)
}
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Find offending rows: `SELECT * FROM <table> WHERE type='parent-child' AND (<target-col> IS NULL)` and fix or delete them.
- Verify DepTargetExpr columns (issue_id/target) are populated for parent-child deps.
- Re-import or repair the dependency data from a known-good export.
- Update bd if the driver version changed column types.
Example fix
// before DELETE FROM dependencies WHERE id = 'broken-row'; // after UPDATE dependencies SET target_id = '<correct-id>' WHERE id = 'broken-row';
Defensive patterns
Strategy: validation
Validate before calling
rows, _ := db.Query("SELECT issue_id, target FROM dependencies WHERE type='parent-child'")
for rows.Next() {
var p, c sql.NullString
_ = rows.Scan(&p, &c)
if !p.Valid || !c.Valid { return fmt.Errorf("parent-child dep with NULL endpoint") }
} Type guard
func isDepScanErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "scan parent-child dep from")
} Try / catch
epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)
if err != nil {
if isDepScanErr(err) { return repairDependencyRowsThenRetry(ctx) }
return err
} Prevention
- Prevent NULL dependency endpoints with DB-level NOT NULL constraints where possible.
- Sanitize dependency data on import/export.
- Don't modify dependencies rows outside bd.
- Re-sync corrupted tables from the primary replica.
When it happens
Trigger: Calling GetEpicsEligibleForClosureInTx when a row from the parent-child dependency query yields NULL or non-convertible values for parent_id/child_id (via DepTargetExpr columns).
Common situations: Rows where the dep target expression evaluates to NULL for a 'parent-child' type; hand-edited or imported dependency rows with missing IDs; driver type-conversion quirks.
Related errors
- scan epic id: %w
- scan child status: %w
- failed to scan dolt_status: %w
- scan dirty config key: %w
- db: ChildCounterSQLRepository.NextChildID: scan: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/14df87f5a1586330.
Report an issue: GitHub.