gastownhall/beads · error
journal: record derived is_blocked update for %s: %w
Error message
journal: record derived is_blocked update for %s: %w
What it means
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.
Source
Thrown at internal/storage/issueops/journal.go:292
var changed []blockedJournalKey
for key, afterBlocked := range after {
if beforeBlocked, existed := before[key]; existed && beforeBlocked != afterBlocked {
changed = append(changed, key)
}
}
sort.Slice(changed, func(i, j int) bool {
if changed[i].table != changed[j].table {
return changed[i].table < changed[j].table
}
return changed[i].id < changed[j].id
})
for _, key := range changed {
// Derived maintenance runs from dozens of mutation paths (recompute /
// mark passes) that carry no actor of their own; attributing the
// triggering mutation here would mean threading an actor through the
// whole blocked-state layer, so these rows record no actor.
if err := RecordEventInTx(ctx, tx, EventUpdate, key.id, ""); err != nil {
return fmt.Errorf("journal: record derived is_blocked update for %s: %w", key.id, err)
}
}
return nil
}
// RecordEventInTx records op for issueID, snapshotting the issue's
// post-mutation state as of tx (read-your-writes within the same transaction).
// Use it for every op except delete (which has no surviving row — use
// RecordDeleteInTx) and dependency ops (use RecordDepEventInTx). A no-op when
// journaling is disabled.
//
// actor is the acting identity that performed the mutation, as resolved for
// the audit-events table; "" when the mutation path genuinely has none
// (derived maintenance, actorless delete plumbing). It is an explicit
// parameter, not ambient context, so a new call site cannot compile without
// deciding attribution.
func RecordEventInTx(ctx context.Context, tx DBTX, op EventOp, issueID, actor string) error {
if !journalEnabled(ctx, tx) {View on GitHub (pinned to 71377f2769)
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
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure all changed ids still exist before recompute+journal
existing, _ := issueops.ExistingIssueIDsInTableInTx(ctx, tx, "issues", changedIDs)
if len(existing) != len(changedIDs) { /* drop deleted ids first */ } Type guard
null
Try / catch
if err := recordBlockedJournalChanges(ctx, tx, snapshot); err != nil {
// wrapped msg includes the failing issue id
log.Printf("blocked journal failed for %v", err)
return err // transaction aborts to keep journal consistent
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Cascade deletes racing with blocked-state recompute; journal table missing/corrupt; connection failure during the journal write.
Related errors
- federation filter: recompute staged is_blocked: %w
- journal dependency removals for batched wisp delete: %w
- recompute is_blocked after source-repo delete: %w
- recompute is_blocked after delete for %s: %w
- journal dependency removals for batch delete: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/0069419186676692.
Report an issue: GitHub.