{"record":{"id":"ec16f0e7ce029ad3","repo":"gastownhall/beads","slug":"affected-by-delete-for-s-w","errorCode":null,"errorMessage":"affected by delete for %s: %w","messagePattern":"affected by delete for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":34,"sourceCode":"const deleteBatchSize = 50\n\n// maxRecursiveResults is the safety limit for the total number of issues\n// discovered during recursive dependent traversal.\nconst maxRecursiveResults = 10000\n\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc DeleteIssueInTx(ctx context.Context, tx *sql.Tx, id string) error {\n\tisWisp := IsActiveWispInTx(ctx, tx, id)\n\n\tvar deletedIssues, deletedWisps []string\n\tif isWisp {\n\t\tdeletedWisps = []string{id}\n\t} else {\n\t\tdeletedIssues = []string{id}\n\t}\n\taffectedIssues, affectedWisps, aerr := AffectedByDeletionInTx(ctx, tx, deletedIssues, deletedWisps)\n\tif aerr != nil {\n\t\treturn fmt.Errorf(\"affected by delete for %s: %w\", id, aerr)\n\t}\n\n\t// Edges are journaled before the rows go, while their source snapshots can\n\t// still be read.\n\tif err := RecordDependencyRemovalsForIssuesInTx(ctx, tx, []string{id}); err != nil {\n\t\treturn fmt.Errorf(\"journal dependency removals for %s: %w\", id, err)\n\t}\n\tif err := deleteIssueRowInTx(ctx, tx, id, isWisp); err != nil {\n\t\treturn err\n\t}\n\n\tif err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {\n\t\treturn fmt.Errorf(\"recompute is_blocked after delete for %s: %w\", id, err)\n\t}\n\n\treturn nil\n}\n","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L16-L52","documentation":"DeleteIssueInTx wraps a failure from AffectedByDeletionInTx — the query that computes which issues/wisps depend on the issue being deleted so their is_blocked flags can be recomputed. The wrapped error (%w) is the underlying SQL/query failure, not a 'not found' condition. The %s is the id of the issue whose deletion triggered the dependency scan.","triggerScenarios":"Calling storage.DeleteIssue / DeleteIssueInTx when AffectedByDeletionInTx fails: the underlying tx is broken (rolled back, timeout, context canceled) or the dependency-plane query fails (connection dropped, lock timeout, driver error).","commonSituations":"Deleting an issue while the DB connection was interrupted mid-transaction; context deadline exceeded because the dependency scan hit a large dependency graph on a slow Dolt server; lock contention with a concurrent writer on dependencies tables.","solutions":["Inspect the wrapped cause (%w) — the error is only a wrapper; the root cause is in err.Unwrap()/the chain.","Check that the caller's context was not canceled/expired before the delete; raise the timeout for large dependency graphs.","Verify DB connectivity and transaction health (no prior error on the same tx — a failed statement can poison the tx).","Retry the whole delete operation on a fresh transaction if the failure was transient (connection reset, deadlock)."],"exampleFix":"// before: reusing a long-lived context that expired mid-delete\nerr := storage.DeleteIssue(ctxWith5sTimeout, db, id)\n// after: use a fresh, adequately-long context per delete\ndelCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nerr := storage.DeleteIssue(delCtx, db, id)","handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil { return fmt.Errorf(\"context already expired before delete: %w\", err) }\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"db unreachable before delete: %w\", err) }","typeGuard":null,"tryCatchPattern":"if err := storage.DeleteIssue(ctx, db, id); err != nil {\n\tif isTransient(err) { // net.Error, driver.ErrBadConn, lock timeout\n\t\t// retry once on a fresh tx + fresh context\n\t}\n\treturn fmt.Errorf(\"delete %s: %w\", id, err)\n}","preventionTips":["Give delete operations a dedicated, generously-sized context instead of reusing request-scoped short deadlines.","Never reuse a transaction handle after any prior error on it.","Watch dependency-graph size before deleting hub issues."],"tags":["database","transaction","error-wrapping","dependency-graph"],"backgroundTag":"transaction-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}