{"record":{"id":"c37eb39cfaad370a","repo":"gastownhall/beads","slug":"journal-resolve-deleted-ids-in-s-w","errorCode":null,"errorMessage":"journal: resolve deleted ids in %s: %w","messagePattern":"journal: resolve deleted ids in (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":343,"sourceCode":"// RecordEventInTx.\nfunc RecordDeleteInTx(ctx context.Context, tx DBTX, issueID, actor string) error {\n\tif !journalEnabled(ctx, tx) {\n\t\treturn nil\n\t}\n\treturn insertEventRow(ctx, tx, EventDelete, issueID, nil, nil, nil, actor)\n}\n\n// journalableDeletesInTx narrows ids to the ones that actually exist in table,\n// so a bulk delete records only rows it really removes. It is a no-op (nil,\n// nil) when journaling is disabled, keeping the extra read off the ordinary\n// local delete path. Callers MUST invoke it before issuing their DELETE.\nfunc journalableDeletesInTx(ctx context.Context, tx DBTX, table string, ids []string) ([]string, error) {\n\tif !journalEnabled(ctx, tx) || len(ids) == 0 {\n\t\treturn nil, nil\n\t}\n\texisting, err := ExistingIssueIDsInTableInTx(ctx, tx, table, ids)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"journal: resolve deleted ids in %s: %w\", table, err)\n\t}\n\treturn existing, nil\n}\n\n// RecordDepEventInTx records a dependency add or remove for issueID, carrying\n// the edge kind and target. The issue snapshot is the post-mutation state as of\n// tx. A no-op when journaling is disabled. actor as on RecordEventInTx.\nfunc RecordDepEventInTx(ctx context.Context, tx DBTX, op EventOp, issueID, kind, target, metadata, actor string) error {\n\tif !journalEnabled(ctx, tx) {\n\t\treturn nil\n\t}\n\tissue, err := getJournalIssueInTx(ctx, tx, issueID)\n\tif err != nil {\n\t\t// The dependency source may itself have been deleted (cascade); record\n\t\t// the edge change with a null snapshot rather than failing.\n\t\tif errors.Is(err, storage.ErrNotFound) {\n\t\t\treturn insertEventRow(ctx, tx, op, issueID, nil, &EventDep{Kind: kind, Target: target, Metadata: metadata}, nil, actor)\n\t\t}","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L325-L361","documentation":"journalableDeletesInTx filters the ids to delete down to those that actually exist in the table (only existing rows get journal delete events). If ExistingIssueIDsInTableInTx fails, the error is wrapped with the table name and the delete operation aborts.","triggerScenarios":"DeleteResolvedSetInTx runs while the existence-check query on the given table fails: table missing, SQL error, connection loss.","commonSituations":"Schema drift removing a helper table; database unavailable during a resolve-set cleanup; permissions issue on the table.","solutions":["Inspect the wrapped driver error to see why the existence query failed","Verify the table exists and matches the expected schema","Check connectivity/permissions","Retry the delete transaction"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"var n int\ndb.QueryRow(\"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = ?\", table).Scan(&n)\nif n == 0 { /* migrate schema before delete-resolve flows */ }","typeGuard":"null","tryCatchPattern":"ids, err := journalableDeletesInTx(ctx, tx, table, ids)\nif err != nil {\n  if isTableNotExistError(err) { /* run migration, then retry */ }\n  return fmt.Errorf(\"resolve deletes failed: %w\", err)\n}","preventionTips":["Run migrations before delete/cleanup jobs","Confirm the table name passed matches the schema","Keep DB connectivity healthy during batch delete flows"],"tags":["journal","delete","sql","existence-check"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}