gastownhall/beads · error
failed to record event: %w
Error message
failed to record event: %w
What it means
After a successful issue update, updateIssueInTx records an audit event via RecordFullEventInTable and wraps any failure as "failed to record event: %w". The issue row may already be updated while the event is missing, so the caller's transaction semantics matter.
Source
Thrown at internal/storage/issueops/update.go:505
//nolint:gosec // G201: issueTable comes from WispTableRouting (hardcoded constants)
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = ?", issueTable, strings.Join(setClauses, ", "))
if _, err := tx.ExecContext(ctx, query, args...); err != nil {
return nil, fmt.Errorf("failed to update issue: %w", err)
}
if clearLease {
if err := DeleteLeaseInTx(ctx, tx, id); err != nil {
return nil, err
}
}
if recordEvent {
oldData, _ := json.Marshal(oldIssue)
newData, _ := json.Marshal(updates)
eventType := DetermineEventType(oldIssue, updates)
if err := RecordFullEventInTable(ctx, tx, eventTable, id, eventType, actor, string(oldData), string(newData)); err != nil {
return nil, fmt.Errorf("failed to record event: %w", err)
}
}
updateResult := &UpdateResult{OldIssue: oldIssue, IsWisp: isWisp, Changed: true, IssueRowsChanged: !isWisp, WispRowsChanged: isWisp}
if rawStatus, hasStatus := updates["status"]; hasStatus {
var newStatus string
switch v := rawStatus.(type) {
case string:
newStatus = v
case types.Status:
newStatus = string(v)
}
oldActive := oldIssue.Status != types.StatusClosed && oldIssue.Status != types.StatusPinned
newActive := newStatus != string(types.StatusClosed) && newStatus != string(types.StatusPinned)
if oldActive != newActive {
var affectedIssues, affectedWisps []string
var aerr error
if isWisp {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped error from RecordFullEventInTable for the root cause.
- Verify eventTable matches the issue/wisp routing for this id.
- Check the events table exists and has capacity for the JSON payloads (old/new data).
- Rely on transaction rollback so the issue update is also reverted; retry the whole update.
Example fix
// before _, err := storage.UpdateIssueInTx(ctx, tx, issueTable, eventTable, id, updates, actor) // after (verify routing before the call) table, evTable := storage.WispTableRouting(isWisp) _, err := storage.UpdateIssueInTx(ctx, tx, table, evTable, id, updates, actor)
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the event table exists/routing is correct before update
table, evTable := storage.WispTableRouting(isWisp)
if evTable == "" {
return errors.New("no event table routed for this issue type")
} Try / catch
if _, err := storage.UpdateIssueInTx(ctx, tx, table, evTable, id, updates, actor); err != nil {
if strings.Contains(err.Error(), "failed to record event") {
// whole tx should roll back; log and retry the full update
return fmt.Errorf("event recording failed, update rolled back: %w", err)
}
return err
} Prevention
- Always pass the correct eventTable constant for issue vs wisp routing.
- Keep old/new JSON payloads within column size limits.
- Rely on transaction rollback so issue and event stay consistent.
- Verify events tables exist in the schema before migrating.
When it happens
Trigger: RecordFullEventInTable fails inside the transaction: event table missing/corrupt, eventTable routing name wrong, oversized old/new JSON payloads exceeding column limits, or DB errors during the event INSERT.
Common situations: Schema missing the events table for wisps; extremely large issue payloads blowing past TEXT limits; Dolt transaction conflicts; passing a wrong eventTable constant for the routed table.
Related errors
- graph create: %w
- reading gate %s: %w
- batch create: %w
- node %q: updating metadata refs: %w
- add label '%s' on %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fa9e26817ee62141.
Report an issue: GitHub.