{"record":{"id":"deb8ad00f1b4270f","repo":"gastownhall/beads","slug":"journal-read-is-blocked-for-s-w","errorCode":null,"errorMessage":"journal: read is_blocked for %s: %w","messagePattern":"journal: read is_blocked for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":406,"sourceCode":"\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) {\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 {","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L388-L424","documentation":"getJournalIssueInTx reads the persisted is_blocked flag with a direct SELECT on the candidate issue table; if that query fails (scan error, table missing, connection issue) the error is wrapped as 'journal: read is_blocked'. This is a journal-only augmentation of the normal issue snapshot.","triggerScenarios":"The is_blocked SELECT fails: issues/deleted_issues table missing or renamed, row deleted between snapshot and probe, driver-level SQL error (lock timeout, connection drop), or Scan type mismatch.","commonSituations":"Schema drift after upgrading beads versions where is_blocked column was added; running against an older/pre-migration database; transient Dolt/driver connection failures mid-transaction.","solutions":["Run schema migrations (bd migrate / bd doctor) so is_blocked exists on the issue tables","Check DB connectivity and retry the operation","Inspect the wrapped %w error (sql.ErrNoRows means the issue vanished concurrently)","Restore from backup if the issues table is corrupted"],"exampleFix":"// before\nops.RecordEventInTx(ctx, tx, op, id, nil, nil) // fails: old schema lacks is_blocked\n// after\nif err := migrate.Run(ctx, db); err != nil { return err } // bring schema current\nops.RecordEventInTx(ctx, tx, op, id, nil, nil)","handlingStrategy":"validation","validationCode":"// ensure schema current before writes\nif err := migrate.Run(ctx, db); err != nil { return err }\nrows, _ := db.Query(\"SHOW COLUMNS FROM issues LIKE 'is_blocked'\")\nif !rows.Next() { return errors.New(\"is_blocked column missing; migrate first\") }","typeGuard":null,"tryCatchPattern":"if err := doJournalWrite(ctx, tx); err != nil {\n    if strings.Contains(err.Error(), \"read is_blocked\") {\n        return fmt.Errorf(\"schema/connection problem: %w\", err)\n    }\n    return err\n}","preventionTips":["Run bd doctor after version upgrades","Keep driver and schema versions aligned","Retry transient SQL errors with backoff"],"tags":["journal","database","schema"],"backgroundTag":"journal-issue-snapshot-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}