gastownhall/beads · error
journal: read is_blocked for %s: %w
Error message
journal: read is_blocked for %s: %w
What it means
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.
Source
Thrown at internal/storage/issueops/journal.go:406
labelTable string
}{
{"issues", "labels"},
{"wisps", "wisp_labels"},
} {
issue, err := getIssueFromTableInTx(ctx, tx, candidate.issueTable, candidate.labelTable, issueID)
if errors.Is(err, storage.ErrNotFound) {
continue
}
if err != nil {
if optionalBlockedTable(candidate.issueTable) && isTableNotExistError(err) {
continue
}
return nil, err
}
var blocked int
//nolint:gosec // candidate.issueTable is one of the two hardcoded values above.
if err := tx.QueryRowContext(ctx, fmt.Sprintf("SELECT is_blocked FROM %s WHERE id = ?", candidate.issueTable), issueID).Scan(&blocked); err != nil {
return nil, fmt.Errorf("journal: read is_blocked for %s: %w", issueID, err)
}
issue.IsBlocked = blocked != 0
return issue, nil
}
return nil, fmt.Errorf("%w: issue %s", storage.ErrNotFound, issueID)
}
// insertEventRow performs the actual INSERT. It is the ONE seam both write
// plumbings funnel through, so the seq mechanism cannot drift between them. A
// nil issue is stored as SQL NULL (deletes); a nil dep is stored as SQL NULL
// (non-dependency ops). ts is the insert time, stamped inside the committing
// transaction. actor is stored as-is — "" for the genuinely unattributable
// paths — in the NOT NULL DEFAULT ” actor column.
func insertEventRow(ctx context.Context, tx DBTX, op EventOp, issueID string, issue *types.Issue, dep *EventDep, comment *EventComment, actor string) error {
var issueJSON any
if issue != nil {
b, err := json.Marshal(issue)
if err != nil {View on GitHub (pinned to 71377f2769)
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
Example fix
// before
ops.RecordEventInTx(ctx, tx, op, id, nil, nil) // fails: old schema lacks is_blocked
// after
if err := migrate.Run(ctx, db); err != nil { return err } // bring schema current
ops.RecordEventInTx(ctx, tx, op, id, nil, nil) Defensive patterns
Strategy: validation
Validate before calling
// ensure schema current before writes
if err := migrate.Run(ctx, db); err != nil { return err }
rows, _ := db.Query("SHOW COLUMNS FROM issues LIKE 'is_blocked'")
if !rows.Next() { return errors.New("is_blocked column missing; migrate first") } Try / catch
if err := doJournalWrite(ctx, tx); err != nil {
if strings.Contains(err.Error(), "read is_blocked") {
return fmt.Errorf("schema/connection problem: %w", err)
}
return err
} Prevention
- Run bd doctor after version upgrades
- Keep driver and schema versions aligned
- Retry transient SQL errors with backoff
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- journal: seed seq counter: %w
- failed to recompute is_blocked: %w
- failed to scan federation peer: %w
- failed to scan comment: %w
- failed to initialize schema: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/deb8ad00f1b4270f.
Report an issue: GitHub.