{"record":{"id":"3897291d6eab50f7","repo":"gastownhall/beads","slug":"journal-dependency-removals-for-s-w","errorCode":null,"errorMessage":"journal dependency removals for %s: %w","messagePattern":"journal dependency removals for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":40,"sourceCode":"//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\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc deleteIssueRowInTx(ctx context.Context, tx *sql.Tx, id string, isWisp bool) error {\n\tissueTable, _, _, _ := WispTableRouting(isWisp)\n\tresult, err := tx.ExecContext(ctx, fmt.Sprintf(\"DELETE FROM %s WHERE id = ?\", issueTable), id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete issue from %s: %w\", issueTable, err)","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L22-L58","documentation":"DeleteIssueInTx wraps a failure from RecordDependencyRemovalsForIssuesInTx, which journals the dependency edges that are about to disappear so consumers can replay/audit removals. This runs inside the same transaction before the issue row is deleted, so a failure here aborts the delete with no data loss.","triggerScenarios":"Calling DeleteIssue/DeleteIssueInTx when the journal insert for dependency removals fails: tx already aborted, context canceled, connection lost, or the journal table write hits a driver/SQL error.","commonSituations":"Network blip between journaling and delete; context deadline exceeded on a slow server; the transaction was already poisoned by an earlier failed statement on the same tx handle.","solutions":["Look at the wrapped cause for the real driver error.","Retry the entire delete in a fresh transaction — the tx aborts atomically, so it is safe to retry.","Ensure the caller passes a live, unexpired context to the delete call.","Check the journal/dependencies tables exist and are writable in the current schema version (run bd migrate if schema is stale)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil { return fmt.Errorf(\"context expired before delete: %w\", err) }","typeGuard":null,"tryCatchPattern":"if err := storage.DeleteIssue(ctx, db, id); err != nil {\n\tvar e *transientDBError\n\tif errors.As(err, &e) { /* retry whole delete in a new tx — the tx rolled back atomically */ }\n\treturn err\n}","preventionTips":["The delete is transactional — safe to retry the entire operation on transient failures.","Keep schema current (bd migrate) so journal tables exist and are writable.","Avoid sharing one context/tx across many long operations."],"tags":["database","transaction","journaling","error-wrapping"],"backgroundTag":"transaction-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}