{"record":{"id":"9de9ceea54bd7a92","repo":"gastownhall/beads","slug":"previewdelete-cascade-expansion-w","errorCode":null,"errorMessage":"previewDelete: cascade expansion: %w","messagePattern":"previewDelete: cascade expansion: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue_delete.go","lineNumber":321,"sourceCode":"\n\tdepRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionOut})\n\tif err != nil {\n\t\treturn preview, fmt.Errorf(\"previewDelete: list deps: %w\", err)\n\t}\n\tfor id, deps := range depRes.Outgoing {\n\t\tpreview.DepRecords[id] = deps\n\t}\n\twispDepRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionOut, UseWispsTable: true})\n\tif err != nil && !dberrors.IsTableNotExist(err) {\n\t\treturn preview, fmt.Errorf(\"previewDelete: list wisp deps: %w\", err)\n\t}\n\tfor id, deps := range wispDepRes.Outgoing {\n\t\tpreview.DepRecords[id] = append(preview.DepRecords[id], deps...)\n\t}\n\n\tallIDs, err := u.issueRepo.FindAllDependents(ctx, ids)\n\tif err != nil {\n\t\treturn preview, fmt.Errorf(\"previewDelete: cascade expansion: %w\", err)\n\t}\n\tdeletedSet := make(map[string]bool, len(allIDs))\n\tfor _, id := range allIDs {\n\t\tdeletedSet[id] = true\n\t}\n\tconnected, _, err := u.collectConnectedIssues(ctx, allIDs, deletedSet)\n\tif err != nil {\n\t\treturn preview, err\n\t}\n\tpreview.ConnectedIssues = connected\n\treturn preview, nil\n}\n\nfunc (u *issueUseCaseImpl) collectConnectedIssues(\n\tctx context.Context, allIDs []string, deletedSet map[string]bool,\n) (map[string]*types.Issue, map[string]bool, error) {\n\tout := map[string]*types.Issue{}\n\tisWisp := map[string]bool{}","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue_delete.go#L303-L339","documentation":"This error wraps a failure from issueRepo.FindAllDependents in previewDelete, which transitively expands the delete set to all dependents (the cascade). Without this expansion the preview could not show which issues disappear when cascade delete is enabled.","triggerScenarios":"PreviewDelete/PreviewDeleteWisp when the recursive dependent-lookup query fails: deep dependency graphs causing expensive/recursive queries, DB timeout, or connection loss.","commonSituations":"Very deep or cyclic-heavy dependency graphs making cascade expansion slow enough to hit statement timeouts; Dolt connection drops during long recursive queries.","solutions":["Increase the query/statement timeout for the cascade lookup.","Inspect the wrapped driver error and fix connectivity.","Preview smaller id subsets to shrink the cascade.","Retry — the preview is read-only and safe to re-run."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Bound the cascade before previewing: preview fewer ids at a time.\nif len(ids) > maxBatch {\n    return fmt.Errorf(\"preview batch too large (%d > %d); split it\", len(ids), maxBatch)\n}","typeGuard":"var dbErr *dberrors.DBError\nif errors.As(err, &dbErr) {\n    // inspect timeout vs connectivity codes\n}","tryCatchPattern":"preview, err := uc.PreviewDelete(ctx, ids)\nif err != nil && strings.Contains(err.Error(), \"previewDelete: cascade expansion\") {\n    // often a timeout on deep graphs — retry with longer timeout or smaller batch\n    return retryWithLongerTimeout(func() error { _, e := uc.PreviewDelete(ctx, ids); return e })\n}","preventionTips":["Increase statement timeouts for deep dependency graphs","Preview in smaller id batches","Watch for runaway dependency chains in your issue graph","Retry — cascade expansion is read-only"],"tags":["go","storage","cascade","preview","recursion"],"backgroundTag":"cascade-expansion-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}