gastownhall/beads · error
db: DependencySQLRepository.Delete: record dependency_remove
Error message
db: DependencySQLRepository.Delete: record dependency_removed event: %w
What it means
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).
Source
Thrown at internal/storage/domain/db/dependency.go:368
if _, err := r.runner.ExecContext(ctx,
fmt.Sprintf("DELETE FROM %s WHERE issue_id = ? AND %s = ?", table, depTargetExpr),
issueID, dependsOnID,
); err != nil {
return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: %s -> %s: %w", issueID, dependsOnID, err)
}
// The type lookup above returned Found:false when no edge existed, so reaching
// here means a row was deleted — record the dependency_removed event on the
// source's event table, matching the embedded/issueops RemoveDependencyInTx path.
// Gated on EmitEvent so only the explicit `bd dep remove` verb emits.
if opts.EmitEvent {
if err := r.events.Record(ctx, domain.Event{
IssueID: issueID,
Type: types.EventDependencyRemoved,
Actor: actor,
NewValue: fmt.Sprintf("Removed dependency on %s", dependsOnID),
}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {
return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: record dependency_removed event: %w", err)
}
}
dt := types.DependencyType(depType)
var affectedIssues, affectedWisps []string
var aerr error
if opts.UseWispsTable {
affectedIssues, affectedWisps, aerr = issueops.AffectedByDepChangeForWispInTx(ctx, r.runner, issueID, dependsOnID, dt)
} else {
affectedIssues, affectedWisps, aerr = issueops.AffectedByDepChangeInTx(ctx, r.runner, issueID, dependsOnID, dt)
}
if aerr != nil {
return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: affected set: %w", aerr)
}
if err := issueops.RecomputeIsBlockedInTx(ctx, r.runner, affectedIssues, affectedWisps); err != nil {
return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: recompute is_blocked: %w", err)
}
View on GitHub (pinned to 71377f2769)
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.
Example fix
// before
res, err := deps.Delete(ctx, issueID, depID, actor, domain.DepInsertOpts{EmitEvent: true, UseWispsTable: false})
// after: retry transient failures; Delete is idempotent via Found:false
var res domain.DepDeleteResult
err = retry.OnError(ctx, isTransient, func() error {
var e error
res, e = deps.Delete(ctx, issueID, depID, actor, domain.DepInsertOpts{EmitEvent: true})
return e
}) Defensive patterns
Strategy: retry
Validate before calling
if issueID == "" || dependsOnID == "" { return errors.New("ids required") }
if err := ctx.Err(); err != nil { return err } Try / catch
err := retry.OnError(ctx, isTransientDBError, func() error {
_, err := deps.Delete(ctx, issueID, depID, actor, opts) // idempotent: Found:false on re-run
return err
})
if err != nil && strings.Contains(err.Error(), "record dependency_removed event") {
// reconcile: edge may be deleted but event missing; re-record or verify sync state
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- db: DependencySQLRepository.Insert: record dependency_added
- db: DependencySQLRepository.Delete: affected set: %w
- db: DependencySQLRepository.Delete: recompute is_blocked: %w
- db: DependencySQLRepository.HasCycle: %w
- db: record event in %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/1b859d0421281701.
Report an issue: GitHub.