{"record":{"id":"fa9e26817ee62141","repo":"gastownhall/beads","slug":"failed-to-record-event-w-fa9e26","errorCode":null,"errorMessage":"failed to record event: %w","messagePattern":"failed to record event: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/update.go","lineNumber":505,"sourceCode":"\t//nolint:gosec // G201: issueTable comes from WispTableRouting (hardcoded constants)\n\tquery := fmt.Sprintf(\"UPDATE %s SET %s WHERE id = ?\", issueTable, strings.Join(setClauses, \", \"))\n\tif _, err := tx.ExecContext(ctx, query, args...); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to update issue: %w\", err)\n\t}\n\n\tif clearLease {\n\t\tif err := DeleteLeaseInTx(ctx, tx, id); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif recordEvent {\n\t\toldData, _ := json.Marshal(oldIssue)\n\t\tnewData, _ := json.Marshal(updates)\n\t\teventType := DetermineEventType(oldIssue, updates)\n\n\t\tif err := RecordFullEventInTable(ctx, tx, eventTable, id, eventType, actor, string(oldData), string(newData)); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to record event: %w\", err)\n\t\t}\n\t}\n\n\tupdateResult := &UpdateResult{OldIssue: oldIssue, IsWisp: isWisp, Changed: true, IssueRowsChanged: !isWisp, WispRowsChanged: isWisp}\n\tif rawStatus, hasStatus := updates[\"status\"]; hasStatus {\n\t\tvar newStatus string\n\t\tswitch v := rawStatus.(type) {\n\t\tcase string:\n\t\t\tnewStatus = v\n\t\tcase types.Status:\n\t\t\tnewStatus = string(v)\n\t\t}\n\t\toldActive := oldIssue.Status != types.StatusClosed && oldIssue.Status != types.StatusPinned\n\t\tnewActive := newStatus != string(types.StatusClosed) && newStatus != string(types.StatusPinned)\n\t\tif oldActive != newActive {\n\t\t\tvar affectedIssues, affectedWisps []string\n\t\t\tvar aerr error\n\t\t\tif isWisp {","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/update.go#L487-L523","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n_, err := storage.UpdateIssueInTx(ctx, tx, issueTable, eventTable, id, updates, actor)\n// after (verify routing before the call)\ntable, evTable := storage.WispTableRouting(isWisp)\n_, err := storage.UpdateIssueInTx(ctx, tx, table, evTable, id, updates, actor)","handlingStrategy":"try-catch","validationCode":"// ensure the event table exists/routing is correct before update\ntable, evTable := storage.WispTableRouting(isWisp)\nif evTable == \"\" {\n    return errors.New(\"no event table routed for this issue type\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := storage.UpdateIssueInTx(ctx, tx, table, evTable, id, updates, actor); err != nil {\n    if strings.Contains(err.Error(), \"failed to record event\") {\n        // whole tx should roll back; log and retry the full update\n        return fmt.Errorf(\"event recording failed, update rolled back: %w\", err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["storage","event","audit","transaction"],"backgroundTag":"event-record-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}