gastownhall/beads · error

record dependency_removed event: %w

Error message

record dependency_removed event: %w

What it means

This error wraps a failure from RecordEventInTable while writing the dependency_removed event row after a dependency edge was successfully deleted, when emitEvent was set (explicit 'bd dep remove'). The edge removal itself succeeded but the history event could not be recorded, so the transaction must roll back to keep the dependency table and event history consistent.

Source

Thrown at internal/storage/issueops/dependencies.go:956

		return false, fmt.Errorf("lookup dependency type for %s -> %s: %w", issueID, dependsOnID, err)
	}

	if _, err := tx.ExecContext(ctx, fmt.Sprintf(
		`DELETE FROM %s WHERE issue_id = ? AND %s = ?`, depTable, DepTargetExpr),
		issueID, dependsOnID); err != nil {
		return false, fmt.Errorf("remove dependency: %w", err)
	}

	// The lookup above returned early when no row matched, so reaching here means
	// an edge was actually deleted. Record the dependency_removed event on the
	// source issue's event table for bd CLI / library history observers — but only
	// when emitEvent is set, so structural removes stay silent (parity with the
	// proxied repo and with the symmetric AddDependencyInTx EmitEvent gate).
	eventWritten := false
	if emitEvent {
		if err := RecordEventInTable(ctx, tx, eventTable, issueID, types.EventDependencyRemoved, actor,
			fmt.Sprintf("Removed dependency on %s", dependsOnID)); err != nil {
			return false, fmt.Errorf("record dependency_removed event: %w", err)
		}
		eventWritten = true
	}

	var affectedIssues, affectedWisps []string
	var aerr error
	if isWisp {
		affectedIssues, affectedWisps, aerr = AffectedByDepChangeForWispInTx(ctx, tx, issueID, dependsOnID, types.DependencyType(depType))
	} else {
		affectedIssues, affectedWisps, aerr = AffectedByDepChangeInTx(ctx, tx, issueID, dependsOnID, types.DependencyType(depType))
	}
	if aerr != nil {
		return false, fmt.Errorf("affected by remove dependency %s -> %s: %w", issueID, dependsOnID, aerr)
	}
	recomputed, err := RecomputeIsBlockedInTxWithResult(ctx, tx, affectedIssues, affectedWisps)
	if err != nil {
		return false, fmt.Errorf("recompute is_blocked after remove dependency %s -> %s: %w", issueID, dependsOnID, err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped driver error from RecordEventInTable; retry the transaction if transient.
  2. Run schema migrations to (re)create the events tables with expected columns.
  3. Free disk space / resolve DB lock contention, then rerun 'bd dep remove'.
  4. If you only need the structural removal without history, use the structural path (emitEvent=false), which skips event writing entirely.

Example fix

// before: event insert fails on missing events table
ok, err := store.RemoveDependencyInTx(ctx, tx, "bd-1", "bd-2", actor, true)
// after: migrate first, then remove
if err := store.Migrate(ctx); err != nil { return err }
ok, err = store.RemoveDependencyInTx(ctx, tx, "bd-1", "bd-2", actor, true)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure event tables exist and are writable before emitEvent removals
for _, t := range []string{"issues_events", "wisps_events"} {
    if err := db.QueryRow("SELECT id FROM " + t + " LIMIT 1").Err(); err != nil {
        return fmt.Errorf("event table %s unavailable; migrate first: %w", t, err)
    }
}

Type guard

func isEventRecordFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "record dependency_removed event")
}

Try / catch

written, err := store.RemoveDependencyInTx(ctx, tx, issueID, dependsOnID, actor, true)
if isEventRecordFailure(err) {
    if isTransient(errors.Unwrap(err)) { return retryTx(removalWithEvent) }
    // else fall back to structural removal without history
    written, err = store.RemoveDependencyInTx(ctx, tx, issueID, dependsOnID, actor, false)
}

Prevention

When it happens

Trigger: RemoveDependencyInTx called with emitEvent=true and the INSERT into the source issue's event table (issues_events or wisps_events per routing) fails — event table missing, event table locked, connection loss, or schema drift on the events table columns.

Common situations: Old database missing the events table or newer event columns; event-table write contention from a concurrent bd process; disk full so the event INSERT fails after the DELETE; manual pruning that dropped event tables.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/dabd0bcf319357e3. Report an issue: GitHub.