{"record":{"id":"4c4e46dd96662cc9","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-delete-affected-set","errorCode":null,"errorMessage":"db: DependencySQLRepository.Delete: affected set: %w","messagePattern":"db: DependencySQLRepository\\.Delete: affected set: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":381,"sourceCode":"\t\t\tIssueID:  issueID,\n\t\t\tType:     types.EventDependencyRemoved,\n\t\t\tActor:    actor,\n\t\t\tNewValue: fmt.Sprintf(\"Removed dependency on %s\", dependsOnID),\n\t\t}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {\n\t\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: record dependency_removed event: %w\", err)\n\t\t}\n\t}\n\n\tdt := types.DependencyType(depType)\n\tvar affectedIssues, affectedWisps []string\n\tvar aerr error\n\tif opts.UseWispsTable {\n\t\taffectedIssues, affectedWisps, aerr = issueops.AffectedByDepChangeForWispInTx(ctx, r.runner, issueID, dependsOnID, dt)\n\t} else {\n\t\taffectedIssues, affectedWisps, aerr = issueops.AffectedByDepChangeInTx(ctx, r.runner, issueID, dependsOnID, dt)\n\t}\n\tif aerr != nil {\n\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: affected set: %w\", aerr)\n\t}\n\tif err := issueops.RecomputeIsBlockedInTx(ctx, r.runner, affectedIssues, affectedWisps); err != nil {\n\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: recompute is_blocked: %w\", err)\n\t}\n\n\t// Snapshot only after all derived blocked-state maintenance has completed.\n\t// Never gated on opts.EmitEvent — a structural removal is as real to a\n\t// replaying consumer as one from an explicit dep verb.\n\tif err := issueops.RecordDepEventInTx(ctx, r.runner, issueops.EventDepRemove, issueID, depType, dependsOnID, depMetadata, actor); err != nil {\n\t\treturn domain.DepDeleteResult{}, err\n\t}\n\n\treturn domain.DepDeleteResult{Found: true, Type: dt, DependsOnID: dependsOnID}, nil\n}\n\nfunc (r *dependencySQLRepositoryImpl) HasCycle(ctx context.Context, issueID, dependsOnID string) (bool, error) {\n\tif issueID == \"\" || dependsOnID == \"\" {\n\t\treturn false, errors.New(\"db: DependencySQLRepository.HasCycle: issueID and dependsOnID must not be empty\")","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L363-L399","documentation":"Wraps a failure from computing the set of issues/wisps affected by a dependency removal (AffectedByDepChangeInTx or AffectedByDepChangeForWispInTx). After the edge is deleted, the repository needs the transitive affected set to refresh is_blocked; if that query fails the delete is aborted with this error so blocked-state never goes stale.","triggerScenarios":"DependencySQLRepository.Delete succeeds in deleting the edge, then the recursive/transitive affected-set query fails: connection loss, query timeout on large dependency graphs, SQL syntax/schema mismatch, or context cancellation mid-query.","commonSituations":"Very large dependency graphs timing out; context deadline exceeded because caller set a short timeout; Dolt server restarted mid-transaction; wrong UseWispsTable flag selecting an empty/absent table.","solutions":["Re-run the Delete with a longer context timeout / larger deadline; it is idempotent (second run returns Found:false but still recomputes nothing needed).","Check ctx cancellation: ensure the caller's context isn't expiring during the transitive query.","Verify opts.UseWispsTable matches where the dependency was stored.","Inspect DB health (locks, connection pool exhaustion) if failures cluster under load."],"exampleFix":"// before\nctx := context.Background() // unbounded or too-short deadline elsewhere\nres, err := deps.Delete(ctx, id, depID, actor, opts)\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nres, err := deps.Delete(ctx, id, depID, actor, opts)","handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil { return err }\nif issueID == \"\" || dependsOnID == \"\" { return errors.New(\"ids required\") }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"affected set\") {\n    // retry with longer deadline; Delete is idempotent (Found:false if already deleted)\n    ctx2, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n    defer cancel()\n    _, err = deps.Delete(ctx2, issueID, depID, actor, opts)\n}","preventionTips":["Use generous context timeouts for deletes on large dependency graphs.","Avoid cancelling the context mid-delete; let the transaction finish.","Keep the wisps/issues table flag consistent across insert and delete.","Check DB health/locks before bulk dep-removal operations."],"tags":["database","dependency","query"],"backgroundTag":"dependency-recompute-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}