gastownhall/beads · error
collectConnected (issues): %w
Error message
collectConnected (issues): %w
What it means
This error wraps a failure from depRepo.ListByIssueIDs with Direction=DepDirectionBoth in collectConnectedIssues, which gathers both incoming and outgoing dependencies for the (expanded) id set to find connected issues surviving the delete. Called by both deleteMany (when UpdateTextReferences is set) and previewDelete; a failure propagates and aborts the whole operation.
Source
Thrown at internal/storage/domain/issue_delete.go:346
if err != nil {
return preview, err
}
preview.ConnectedIssues = connected
return preview, nil
}
func (u *issueUseCaseImpl) collectConnectedIssues(
ctx context.Context, allIDs []string, deletedSet map[string]bool,
) (map[string]*types.Issue, map[string]bool, error) {
out := map[string]*types.Issue{}
isWisp := map[string]bool{}
if len(allIDs) == 0 {
return out, isWisp, nil
}
issueRes, err := u.depRepo.ListByIssueIDs(ctx, allIDs, DepListOpts{Direction: DepDirectionBoth})
if err != nil {
return nil, nil, fmt.Errorf("collectConnected (issues): %w", err)
}
wispRes, err := u.depRepo.ListByIssueIDs(ctx, allIDs, DepListOpts{Direction: DepDirectionBoth, UseWispsTable: true})
if err != nil && !dberrors.IsTableNotExist(err) {
return nil, nil, fmt.Errorf("collectConnected (wisps): %w", err)
}
neighbors := map[string]bool{}
accumulate := func(m map[string][]*types.Dependency) {
for _, deps := range m {
for _, d := range deps {
for _, candidate := range [2]string{d.IssueID, d.DependsOnID} {
if candidate == "" || deletedSet[candidate] {
continue
}
neighbors[candidate] = true
}
}
}View on GitHub (pinned to 71377f2769)
Solutions
- Fix the wrapped driver-level error (connectivity, timeout, locks).
- Retry the delete; dependency deletion by ID is idempotent, so re-runs are safe.
- Chunk the delete when the cascade id set is very large.
- Enable text-reference rewriting only when needed to skip this work path.
Defensive patterns
Strategy: retry
Validate before calling
// Check connectivity and bound the cascade before deleting with text rewriting:
if err := store.PingContext(ctx); err != nil {
return fmt.Errorf("db unreachable: %w", err)
}
if params.UpdateTextReferences && len(ids) > maxBatch {
return fmt.Errorf("delete batch too large for text rewriting: %d", len(ids))
} Type guard
var dbErr *dberrors.DBError
if errors.As(err, &dbErr) {
// classify driver failure before deciding retry vs abort
} Try / catch
res, err := uc.DeleteIssues(ctx, ids, opts)
if err != nil && strings.Contains(err.Error(), "collectConnected (issues)") {
// deletes are idempotent by id; safe to retry after transient failure
return retryWithBackoff(func() error { _, e := uc.DeleteIssues(ctx, ids, opts); return e })
} Prevention
- Keep delete batches small when UpdateTextReferences is enabled
- Retry after transient failures — dependency deletes are idempotent
- Increase timeouts for wide cascades
- Disable text-reference rewriting when not needed
When it happens
Trigger: DeleteIssues/DeleteWisps with text-reference rewriting enabled, or PreviewDelete, when the bidirectional dependency query on the main table fails: DB outage, timeout on large allIDs sets, scan error.
Common situations: Large cascades making allIDs huge and the bulk query heavy; connection drops mid-delete (after rows were already removed — see partial-apply risk); lock contention on dependencies.
Related errors
- collectConnected (wisps): %w
- no store is open for this workspace
- load dependency records: %w
- failed to remove relates-to %s -> %s: %w
- failed to get dependencies for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/df93c0c898e7e6f4.
Report an issue: GitHub.