{"record":{"id":"155a08cf9c679b41","repo":"gastownhall/beads","slug":"db-record-event-in-s-w","errorCode":null,"errorMessage":"db: record event in %s: %w","messagePattern":"db: record event in (.+?): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/events.go","lineNumber":29,"sourceCode":")\n\nfunc NewEventsSQLRepository(runner Runner) domain.EventsSQLRepository {\n\treturn &eventsSQLRepositoryImpl{runner: runner}\n}\n\ntype eventsSQLRepositoryImpl struct {\n\trunner Runner\n}\n\nvar _ domain.EventsSQLRepository = (*eventsSQLRepositoryImpl)(nil)\n\nfunc (r *eventsSQLRepositoryImpl) Record(ctx context.Context, evt domain.Event, opts domain.RecordEventOpts) error {\n\ttable := \"events\"\n\tif opts.UseWispsTable {\n\t\ttable = \"wisp_events\"\n\t}\n\tif err := issueops.RecordFullEventInTable(ctx, r.runner, table, evt.IssueID, evt.Type, evt.Actor, evt.OldValue, evt.NewValue); err != nil {\n\t\treturn fmt.Errorf(\"db: record event in %s: %w\", table, err)\n\t}\n\treturn nil\n}\n\nfunc (r *eventsSQLRepositoryImpl) DeleteAllForIDs(ctx context.Context, ids []string, opts domain.RecordEventOpts) (int, error) {\n\tif len(ids) == 0 {\n\t\treturn 0, nil\n\t}\n\ttable := \"events\"\n\tif opts.UseWispsTable {\n\t\ttable = \"wisp_events\"\n\t}\n\ttotal := 0\n\tfor start := 0; start < len(ids); start += deleteBatchSize {\n\t\tend := start + deleteBatchSize\n\t\tif end > len(ids) {\n\t\t\tend = len(ids)\n\t\t}","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/events.go#L11-L47","documentation":"Wraps failures recording an event row into either the events or wisp_events table via issueops.RecordFullEventInTable. The %s names which table was targeted. Unlike some wisp paths, this does not tolerate missing tables, so any insert failure surfaces here.","triggerScenarios":"Calling Record with opts.UseWispsTable=false and a missing/nonexistent events table, an invalid evt.IssueID violating a foreign key, oversized old/new values, connection failure, or a read-only replica.","commonSituations":"Recording an event for an issue that was deleted mid-transaction (FK violation); database in read-only mode; schema not yet migrated so the events table is absent.","solutions":["Check the wrapped driver error for FK violations or missing-table errors","Run schema migrations so the events table exists (bd migrate / dolt schema ensure)","Verify the target issue exists before recording the event","Check disk/replication state if the DB is read-only"],"exampleFix":"// before\nerr := eventsRepo.Record(ctx, evt, domain.RecordEventOpts{})\n// after\nif _, err := issueRepo.Get(ctx, evt.IssueID, opts); err == nil {\n    err = eventsRepo.Record(ctx, evt, domain.RecordEventOpts{})\n}","handlingStrategy":"try-catch","validationCode":"if evt.IssueID == \"\" { return fmt.Errorf(\"event requires IssueID\") }\nif _, err := issueRepo.Get(ctx, evt.IssueID, tableOpts); err != nil {\n    return fmt.Errorf(\"target issue %s missing, skipping event\", evt.IssueID)\n}","typeGuard":"func isFKViolation(err error) bool {\n    var mysqlErr *go_mysql.MySQLError\n    return errors.As(err, &mysqlErr) && mysqlErr.Number == 1452\n}","tryCatchPattern":"if err := eventsRepo.Record(ctx, evt, opts); err != nil {\n    if isFKViolation(err) { log.Warnf(\"event for deleted issue %s dropped\", evt.IssueID); return nil }\n    return fmt.Errorf(\"record event: %w\", err)\n}","preventionTips":["Verify the target issue exists before recording","Keep old/new values within column size limits","Run migrations so events/wisp_events exist","Avoid recording events on read-only replicas"],"tags":["database","events","insert"],"backgroundTag":"event-record-insert-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}