{"record":{"id":"1b859d0421281701","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-delete-record-depende","errorCode":null,"errorMessage":"db: DependencySQLRepository.Delete: record dependency_removed event: %w","messagePattern":"db: DependencySQLRepository\\.Delete: record dependency_removed event: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":368,"sourceCode":"\tif _, err := r.runner.ExecContext(ctx,\n\t\tfmt.Sprintf(\"DELETE FROM %s WHERE issue_id = ? AND %s = ?\", table, depTargetExpr),\n\t\tissueID, dependsOnID,\n\t); err != nil {\n\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: %s -> %s: %w\", issueID, dependsOnID, err)\n\t}\n\n\t// The type lookup above returned Found:false when no edge existed, so reaching\n\t// here means a row was deleted — record the dependency_removed event on the\n\t// source's event table, matching the embedded/issueops RemoveDependencyInTx path.\n\t// Gated on EmitEvent so only the explicit `bd dep remove` verb emits.\n\tif opts.EmitEvent {\n\t\tif err := r.events.Record(ctx, domain.Event{\n\t\t\tIssueID:  issueID,\n\t\t\tType:     types.EventDependencyRemoved,\n\t\t\tActor:    actor,\n\t\t\tNewValue: fmt.Sprintf(\"Removed dependency on %s\", dependsOnID),\n\t\t}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {\n\t\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: record dependency_removed event: %w\", err)\n\t\t}\n\t}\n\n\tdt := types.DependencyType(depType)\n\tvar affectedIssues, affectedWisps []string\n\tvar aerr error\n\tif opts.UseWispsTable {\n\t\taffectedIssues, affectedWisps, aerr = issueops.AffectedByDepChangeForWispInTx(ctx, r.runner, issueID, dependsOnID, dt)\n\t} else {\n\t\taffectedIssues, affectedWisps, aerr = issueops.AffectedByDepChangeInTx(ctx, r.runner, issueID, dependsOnID, dt)\n\t}\n\tif aerr != nil {\n\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: affected set: %w\", aerr)\n\t}\n\tif err := issueops.RecomputeIsBlockedInTx(ctx, r.runner, affectedIssues, affectedWisps); err != nil {\n\t\treturn domain.DepDeleteResult{}, fmt.Errorf(\"db: DependencySQLRepository.Delete: recompute is_blocked: %w\", err)\n\t}\n","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L350-L386","documentation":"This wraps a failure from recording the 'dependency_removed' event in the events table after a dependency edge was successfully deleted. The library throws it because the DELETE succeeded but the audit/event record needed for sync and replay could not be written, so the whole Delete is failed rather than leaving a silent gap in the event log. The dependency row itself is NOT rolled back by this wrapper alone (unless the caller wraps in a transaction).","triggerScenarios":"Calling DependencySQLRepository.Delete(ctx, issueID, dependsOnID, actor, opts) with opts.EmitEvent=true; the row is deleted, then r.events.Record fails (events table missing/corrupt, DB connection dropped, UseWispsTable pointing at a wisps event table that does not exist, or constraint violation on the event insert).","commonSituations":"Dolt/MySQL connection interrupted mid-delete; wisps-table flag mismatch (edge deleted from issues deps but events recorded against missing wisps event table); events table schema migration drift; disk-full or lock-timeout on the events table.","solutions":["Check DB connectivity and re-run the Delete (it is idempotent: a re-run returns Found:false if the edge is already gone).","Verify opts.UseWispsTable matches the table the dependency actually lives in, so events.Record targets the right events table.","Inspect the events table schema/state (bd dolt / SQL shell) for corruption or missing migration, and re-run migrations.","If the event was partially recorded, reconcile by re-emitting the dependency_removed event or removing the duplicate edge/event pair."],"exampleFix":"// before\nres, err := deps.Delete(ctx, issueID, depID, actor, domain.DepInsertOpts{EmitEvent: true, UseWispsTable: false})\n// after: retry transient failures; Delete is idempotent via Found:false\nvar res domain.DepDeleteResult\nerr = retry.OnError(ctx, isTransient, func() error {\n    var e error\n    res, e = deps.Delete(ctx, issueID, depID, actor, domain.DepInsertOpts{EmitEvent: true})\n    return e\n})","handlingStrategy":"retry","validationCode":"if issueID == \"\" || dependsOnID == \"\" { return errors.New(\"ids required\") }\nif err := ctx.Err(); err != nil { return err }","typeGuard":null,"tryCatchPattern":"err := retry.OnError(ctx, isTransientDBError, func() error {\n    _, err := deps.Delete(ctx, issueID, depID, actor, opts) // idempotent: Found:false on re-run\n    return err\n})\nif err != nil && strings.Contains(err.Error(), \"record dependency_removed event\") {\n    // reconcile: edge may be deleted but event missing; re-record or verify sync state\n}","preventionTips":["Wrap Delete in a DB transaction so edge deletion and event recording commit atomically.","Keep UseWispsTable consistent with where the dependency was inserted.","Run pending Dolt migrations before upgrades that touch events tables.","Monitor DB connectivity; Delete is idempotent so safe to retry on transient errors."],"tags":["database","events","dependency"],"backgroundTag":"event-record-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}