{"record":{"id":"0069419186676692","repo":"gastownhall/beads","slug":"journal-record-derived-is-blocked-update-for-s","errorCode":null,"errorMessage":"journal: record derived is_blocked update for %s: %w","messagePattern":"journal: record derived is_blocked update for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":292,"sourceCode":"\tvar changed []blockedJournalKey\n\tfor key, afterBlocked := range after {\n\t\tif beforeBlocked, existed := before[key]; existed && beforeBlocked != afterBlocked {\n\t\t\tchanged = append(changed, key)\n\t\t}\n\t}\n\tsort.Slice(changed, func(i, j int) bool {\n\t\tif changed[i].table != changed[j].table {\n\t\t\treturn changed[i].table < changed[j].table\n\t\t}\n\t\treturn changed[i].id < changed[j].id\n\t})\n\tfor _, key := range changed {\n\t\t// Derived maintenance runs from dozens of mutation paths (recompute /\n\t\t// mark passes) that carry no actor of their own; attributing the\n\t\t// triggering mutation here would mean threading an actor through the\n\t\t// whole blocked-state layer, so these rows record no actor.\n\t\tif err := RecordEventInTx(ctx, tx, EventUpdate, key.id, \"\"); err != nil {\n\t\t\treturn fmt.Errorf(\"journal: record derived is_blocked update for %s: %w\", key.id, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// RecordEventInTx records op for issueID, snapshotting the issue's\n// post-mutation state as of tx (read-your-writes within the same transaction).\n// Use it for every op except delete (which has no surviving row — use\n// RecordDeleteInTx) and dependency ops (use RecordDepEventInTx). A no-op when\n// journaling is disabled.\n//\n// actor is the acting identity that performed the mutation, as resolved for\n// the audit-events table; \"\" when the mutation path genuinely has none\n// (derived maintenance, actorless delete plumbing). It is an explicit\n// parameter, not ambient context, so a new call site cannot compile without\n// deciding attribution.\nfunc RecordEventInTx(ctx context.Context, tx DBTX, op EventOp, issueID, actor string) error {\n\tif !journalEnabled(ctx, tx) {","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L274-L310","documentation":"recordBlockedJournalChanges records an EventUpdate journal row for each issue whose derived is_blocked changed. If RecordEventInTx fails for any changed id, the error is wrapped with the issue id and the whole transaction aborts so the journal never misses a blocked-state transition.","triggerScenarios":"One of the changed issue ids no longer exists (its row was deleted mid-transaction) so getJournalIssueInTx inside RecordEventInTx fails, or the journal insert itself hits a SQL error.","commonSituations":"Cascade deletes racing with blocked-state recompute; journal table missing/corrupt; connection failure during the journal write.","solutions":["Identify the failing issue id from the wrapped message and the underlying cause","Check whether the issue row was deleted concurrently (delete-before-recompute ordering)","Ensure the journal table exists and is healthy","Retry the operation; if deletes race recomputes, reorder so deletes complete first"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"// ensure all changed ids still exist before recompute+journal\nexisting, _ := issueops.ExistingIssueIDsInTableInTx(ctx, tx, \"issues\", changedIDs)\nif len(existing) != len(changedIDs) { /* drop deleted ids first */ }","typeGuard":"null","tryCatchPattern":"if err := recordBlockedJournalChanges(ctx, tx, snapshot); err != nil {\n  // wrapped msg includes the failing issue id\n  log.Printf(\"blocked journal failed for %v\", err)\n  return err // transaction aborts to keep journal consistent\n}","preventionTips":["Complete cascade deletes before recompute passes in the same transaction","Ensure the journal table exists and is writable","Serialize delete and blocked-recompute operations per issue"],"tags":["journal","is-blocked","transaction","consistency"],"backgroundTag":"journal-record-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}