{"record":{"id":"1d47fd413493ec3f","repo":"gastownhall/beads","slug":"w-issue-s-1d47fd","errorCode":null,"errorMessage":"%w: issue %s","messagePattern":"%w: issue (.+?)","errorType":"exception","errorClass":"storage.ErrNotFound","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":411,"sourceCode":"\t\tissue, err := getIssueFromTableInTx(ctx, tx, candidate.issueTable, candidate.labelTable, issueID)\n\t\tif errors.Is(err, storage.ErrNotFound) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tif optionalBlockedTable(candidate.issueTable) && isTableNotExistError(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tvar blocked int\n\t\t//nolint:gosec // candidate.issueTable is one of the two hardcoded values above.\n\t\tif err := tx.QueryRowContext(ctx, fmt.Sprintf(\"SELECT is_blocked FROM %s WHERE id = ?\", candidate.issueTable), issueID).Scan(&blocked); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"journal: read is_blocked for %s: %w\", issueID, err)\n\t\t}\n\t\tissue.IsBlocked = blocked != 0\n\t\treturn issue, nil\n\t}\n\treturn nil, fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, issueID)\n}\n\n// insertEventRow performs the actual INSERT. It is the ONE seam both write\n// plumbings funnel through, so the seq mechanism cannot drift between them. A\n// nil issue is stored as SQL NULL (deletes); a nil dep is stored as SQL NULL\n// (non-dependency ops). ts is the insert time, stamped inside the committing\n// transaction. actor is stored as-is — \"\" for the genuinely unattributable\n// paths — in the NOT NULL DEFAULT ” actor column.\nfunc insertEventRow(ctx context.Context, tx DBTX, op EventOp, issueID string, issue *types.Issue, dep *EventDep, comment *EventComment, actor string) error {\n\tvar issueJSON any\n\tif issue != nil {\n\t\tb, err := json.Marshal(issue)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"journal: marshal issue %s: %w\", issueID, err)\n\t\t}\n\t\tissueJSON = string(b)\n\t}\n\tvar depJSON any","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L393-L429","documentation":"getJournalIssueInTx returns storage.ErrNotFound wrapped with the issue ID when neither candidate issue table (issues or deleted issues) contains a snapshot row for the ID. It is the sentinel path for 'this journal event has no issue to snapshot'.","triggerScenarios":"Recording any journal event (RecordEventInTx, RecordDepEventInTx, RecordCommentEventInTx) for an issue ID absent from both issues and the tombstone table — e.g. the issue was never created, was purged, or the ID is mistyped.","commonSituations":"Sync/import scripts referencing IDs from another database; commenting on or updating an issue deleted by another worker; tests using fabricated IDs; cross-repo ID confusion after re-cloning.","solutions":["Verify the issue exists: bd show <id> before mutating","Create the issue first if it never existed","Check for concurrent deletion; re-sync or skip the event","errors.Is(err, storage.ErrNotFound) to branch on this case explicitly"],"exampleFix":"// before\nif err := ops.RecordEventInTx(ctx, tx, op, id, nil, nil); err != nil { return err }\n// after\nif err := ops.RecordEventInTx(ctx, tx, op, id, nil, nil); err != nil {\n    if errors.Is(err, storage.ErrNotFound) { return fmt.Errorf(\"skipping %s: issue gone\", id) }\n    return err\n}","handlingStrategy":"type-guard","validationCode":"var exists bool\n_ = tx.QueryRowContext(ctx, \"SELECT EXISTS(SELECT 1 FROM issues WHERE id = ?)\", id).Scan(&exists)\nif !exists { return fmt.Errorf(\"cannot journal unknown issue %s\", id) }","typeGuard":"func isIssueNotFound(err error) bool {\n    return errors.Is(err, storage.ErrNotFound)\n}","tryCatchPattern":"if err := ops.RecordEventInTx(ctx, tx, op, id, nil, nil); err != nil {\n    if isIssueNotFound(err) { return skipEvent(id) }\n    return err\n}","preventionTips":["Validate IDs with bd show before scripting mutations","Handle deleted issues explicitly in sync/import jobs","Use IDs returned by the API, never fabricated ones"],"tags":["journal","not-found","sentinel-error"],"backgroundTag":"journal-issue-snapshot-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}