gastownhall/beads · error

failed to get parent-child deps from %s: %w

Error message

failed to get parent-child deps from %s: %w

What it means

The function then loads parent-child dependencies from each dependencies table (issues and, optionally, wisps). If querying a table fails — and it is not the tolerable 'wisp_dependencies missing on pre-migration DB' case — the error is wrapped with the offending table name. It means the parent-child relationship data could not be read, so closure eligibility cannot be computed.

Source

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

	// Step 2: Get parent-child dependencies from both tables (bd-w2w)
	// Wisp children store their parent-child deps in wisp_dependencies,
	// so we must check both tables to find all children of an epic.
	epicChildMap := make(map[string][]string)
	epicSet := make(map[string]bool, len(epicIDs))
	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...)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Note the table name in the message and inspect its schema against bd's expected columns.
  2. Run pending migrations so dependencies/wisp_dependencies match the current schema.
  3. Retry if the wrapped cause is a transient lock or connection error.
  4. Check the wrapped error for 'unknown column' and repair or recreate the dependencies table.
Defensive patterns

Strategy: validation

Validate before calling

for _, t := range []string{"dependencies", "wisp_dependencies"} {
    if !tableExists(db, t) { return fmt.Errorf("%s missing; run migrations", t) }
}

Type guard

func isDepQueryErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to get parent-child deps from")
}

Try / catch

epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)
if err != nil {
    if isDepQueryErr(err) {
        if table := extractTable(err); !tableExists(db, table) { return migrateThenRetry(ctx) }
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetEpicsEligibleForClosureInTx when the `SELECT ... FROM <depTable> WHERE type='parent-child'` query fails on a table that does exist — connection error, missing expected columns (DepTargetExpr), or a corrupt dependencies table.

Common situations: Schema drift where `dependencies` lacks columns bd expects (older bd created the DB); database locks from a concurrent writer; wisp_dependencies existing but malformed.

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/5966565157703667. Report an issue: GitHub.