gastownhall/beads · error
failed to batch-fetch epic issues: %w
Error message
failed to batch-fetch epic issues: %w
What it means
After mapping epics to children, the function bulk-loads the epic issues themselves via GetIssuesByIDsInTx. A failure there is wrapped as 'failed to batch-fetch epic issues'. It means the final step of building epicIssueMap failed, so closure decisions cannot include full issue details.
Source
Thrown at internal/storage/issueops/epic_closure.go:116
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)
}
epicIssueMap := make(map[string]*types.Issue, len(epicIssues))
for _, issue := range epicIssues {
epicIssueMap[issue.ID] = issue
}
// Step 5: Build results from cached data
var results []*types.EpicStatus
for _, epicID := range epicIDs {
children := epicChildMap[epicID]
if len(children) == 0 {
continue
}
issue, ok := epicIssueMap[epicID]
if !ok || issue == nil {
continue
}View on GitHub (pinned to 71377f2769)
Solutions
- Retry the command; the wrapped cause from GetIssuesByIDsInTx names the real failure.
- Check DB connectivity and server logs for mid-query disconnects.
- Run migrations if the wrapped error mentions unknown tables/columns.
- Upgrade bd if the wrapped error indicates an internal query bug.
Defensive patterns
Strategy: retry
Validate before calling
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("db unavailable: %w", err) }
if len(epicIDs) == 0 { return nil } // avoid odd empty-batch paths Type guard
func isEpicBatchFetchErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "failed to batch-fetch epic issues:")
} Try / catch
epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)
if err != nil {
if isEpicBatchFetchErr(err) && isTransient(err) {
return retryWithBackoff(ctx, 3)
}
return err
} Prevention
- Use a single healthy transaction/connection for the whole closure run.
- Retry transient failures with backoff.
- Keep schema in sync with the bd version.
- Watch for mid-query disconnects in server logs.
When it happens
Trigger: Calling GetEpicsEligibleForClosureInTx when GetIssuesByIDsInTx fails for the collected epic IDs — internal query error, connection drop, or the issues table missing expected columns.
Common situations: The same connection dying mid-function after earlier queries succeeded; schema drift in the issues table; an internal bug/regression in GetIssuesByIDsInTx on unusual ID sets (e.g. empty list edge cases).
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
- failed to get epics: %w
- failed to batch-fetch child statuses from %s: %w
- get events: %w
- querying epics: %w
- querying blocked issues: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b83b4b01b86484de.
Report an issue: GitHub.