gastownhall/beads · error

descendants: hydrate issues: %w

Error message

descendants: hydrate issues: %w

What it means

GetDescendants wraps a failure from fetchIssuesByIDs when hydrating the durable issue rows matched by the descendants walk. After the CTE returns IDs, each ID is loaded from the issues table (with optional label/dependency hydration); any SQL or hydration error there is wrapped as "descendants: hydrate issues". Unlike the wisps hydration, table-not-exist is NOT tolerated here because issues is a mandatory table.

Source

Thrown at internal/storage/domain/db/issue_descendants.go:90

	var wispPred predBundle
	if walkWisps {
		wispPred = buildDescendantsPred("wisps", "w", "wisp_matches", wispWhereClauses, wispArgs)
	}

	cte, allArgs := buildDescendantsCTE(rootID, walkWisps, issuePred, wispPred)

	rows, err := r.runner.QueryContext(ctx, cte, allArgs...)
	if err != nil {
		return nil, fmt.Errorf("descendants: query: %w", err)
	}
	page, err := scanIDSrcPage(rows)
	if err != nil {
		return nil, fmt.Errorf("descendants: %w", err)
	}

	issuesByID, err := r.fetchIssuesByIDs(ctx, page.issueIDs, issuesFilterTables, filter)
	if err != nil {
		return nil, fmt.Errorf("descendants: hydrate issues: %w", err)
	}

	var wispsByID map[string]*types.Issue
	if len(page.wispIDs) > 0 {
		wispsByID, err = r.fetchIssuesByIDs(ctx, page.wispIDs, wispsFilterTables, filter)
		if err != nil && !dberrors.IsTableNotExist(err) {
			return nil, fmt.Errorf("descendants: hydrate wisps: %w", err)
		}
	}

	return reassembleBySrc(page.ordered, issuesByID, wispsByID), nil
}

// buildDescendantsCTE walks parent-child edges AND the dotted-ID fallback the
// classic ParentID filter applies (issueops/filters.go): a row named
// <node>.<suffix> with no parent-child edge at all is a child of <node>.
// Rows carry a via marker: 'e' for edge-found, 'd' for dotted-found. Dotted
// recursion only fires from 'e' rows — a dotted node's own dotted

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error to see whether the base fetch or the label/dependency hydration failed
  2. Run bd doctor to check mandatory tables (issues, labels, dependencies) exist and are consistent
  3. Retry on transient connection errors; shrink the result set with filter fields or MaxRows if the batch is huge
  4. Restore the database from backup if a required table is missing

Example fix

// before: assuming any hydration failure is transient
issues, err := repo.GetDescendants(ctx, root, filter)
// after: distinguish and diagnose
issues, err := repo.GetDescendants(ctx, root, filter)
if err != nil && strings.Contains(err.Error(), "hydrate issues") {
    log.Printf("run bd doctor: mandatory table problem: %v", err)
}
Defensive patterns

Strategy: validation

Validate before calling

if err := bdDoctor(ctx); err != nil {
    return fmt.Errorf("mandatory tables broken, fix before GetDescendants: %w", err)
}

Try / catch

issues, err := repo.GetDescendants(ctx, rootID, filter)
if err != nil && strings.Contains(err.Error(), "hydrate issues") {
    if dberrors.IsTableNotExist(errors.Unwrap(err)) {
        // schema is missing a mandatory table: restore/repair, do not retry
    }
}

Prevention

When it happens

Trigger: Calling GetDescendants when the follow-up `SELECT ... FROM issues WHERE id IN (...)` or its hydrateIssues step (labels/dependencies) fails: missing issues/labels/dependencies tables, connection drop, scan error, or ctx cancellation.

Common situations: Corrupted or partially migrated database missing the labels or dependencies tables; too many descendant IDs producing an oversized IN clause / packet; connection pool exhausted during hydration.

Related errors


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