{"record":{"id":"e84b6848a57343bf","repo":"gastownhall/beads","slug":"journal-snapshot-comment-for-s-w","errorCode":null,"errorMessage":"journal: snapshot comment for %s: %w","messagePattern":"journal: snapshot comment for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":376,"sourceCode":"\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}\n\t\treturn fmt.Errorf(\"journal: snapshot %s for %s: %w\", op, issueID, err)\n\t}\n\treturn insertEventRow(ctx, tx, op, issueID, issue, &EventDep{Kind: kind, Target: target, Metadata: metadata}, nil, actor)\n}\n\n// RecordCommentEventInTx records a replayable structured or audit comment. The\n// journal row's actor is the comment's Author — the identity that wrote it —\n// so this takes no separate actor parameter.\nfunc RecordCommentEventInTx(ctx context.Context, tx DBTX, issueID string, comment *EventComment) 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\treturn fmt.Errorf(\"journal: snapshot comment for %s: %w\", issueID, err)\n\t}\n\treturn insertEventRow(ctx, tx, EventCommentWrite, issueID, issue, nil, comment, comment.Author)\n}\n\n// getJournalIssueInTx augments the normal issue snapshot with the persisted\n// readiness projection. is_blocked is deliberately not part of ordinary issue\n// hydration, but it is required in a journal snapshot because a dependency\n// delta must be replayable without re-running graph maintenance.\nfunc getJournalIssueInTx(ctx context.Context, tx DBTX, issueID string) (*types.Issue, error) {\n\tfor _, candidate := range []struct {\n\t\tissueTable string\n\t\tlabelTable string\n\t}{\n\t\t{\"issues\", \"labels\"},\n\t\t{\"wisps\", \"wisp_labels\"},\n\t} {\n\t\tissue, err := getIssueFromTableInTx(ctx, tx, candidate.issueTable, candidate.labelTable, issueID)\n\t\tif errors.Is(err, storage.ErrNotFound) {","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L358-L394","documentation":"RecordCommentEventInTx wraps any failure from getJournalIssueInTx (fetching the issue snapshot plus its readiness projection) when recording a comment event in the bd_events_journal. The journal needs a full issue snapshot for every comment write, so if the snapshot lookup fails the whole comment transaction fails with this wrapped error.","triggerScenarios":"Calling AddCommentEventInTx, addIssueCommentInTx, or CreateIssueInTxWithResult for an issue ID that does not exist in the issues table (or in the tombstone/deleted-issues table checked by getJournalIssueInTx), or a DB error while reading the issue or its is_blocked column.","commonSituations":"Commenting on an issue that was deleted concurrently in another transaction; a typo'd or stale issue ID passed by an importer/sync tool; corrupted or missing journal schema so the is_blocked probe query fails.","solutions":["Verify the issue ID exists (bd show <id> or SELECT) before adding a comment","Check whether the issue was deleted or renamed concurrently; re-fetch the ID","Run bd doctor / schema checks to confirm issues and journal tables exist","Inspect the wrapped %w error for the underlying SQL cause"],"exampleFix":"// before\nerr := ops.AddCommentEventInTx(ctx, tx, \"bd-9999\", comment) // id never existed\n// after\nvar n int\n_ = tx.QueryRowContext(ctx, \"SELECT COUNT(*) FROM issues WHERE id = ?\", id).Scan(&n)\nif n == 0 { return fmt.Errorf(\"issue %s not found\", id) }\nerr := ops.AddCommentEventInTx(ctx, tx, id, comment)","handlingStrategy":"validation","validationCode":"var n int\nif err := tx.QueryRowContext(ctx, \"SELECT COUNT(*) FROM issues WHERE id = ?\", issueID).Scan(&n); err != nil { return err }\nif n == 0 { return fmt.Errorf(\"issue %s does not exist\", issueID) }","typeGuard":null,"tryCatchPattern":"if err := ops.RecordCommentEventInTx(ctx, tx, id, c); err != nil {\n    if errors.Is(err, storage.ErrNotFound) { /* skip or create issue */ }\n    return fmt.Errorf(\"comment journaling failed: %w\", err)\n}","preventionTips":["Resolve issue IDs through the store API, never free-form strings","Check for concurrent deletes before commenting in sync jobs","Keep schema migrated so the is_blocked probe succeeds"],"tags":["journal","database","not-found"],"backgroundTag":"journal-issue-snapshot-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}