gastownhall/beads · error

retarget inbound dependencies to wisp in %s for %s: %w

Error message

retarget inbound dependencies to wisp in %s for %s: %w

What it means

This error wraps a SQL failure from the UPDATE that rewrites inbound dependency edges pointing at an issue (depends_on_issue_id = id) so they instead point at a wisp (depends_on_wisp_id = id) during an issue-to-wisp move. It is thrown by RetargetInboundDependenciesToWispInTx, which runs the same UPDATE against both the dependencies and wisp_dependencies tables inside the caller's transaction. The wrapped %w carries the underlying driver error (constraint violation, table missing, connection loss, etc.).

Source

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

		return nil
	}
	return value.Time
}

func RetargetInboundDependenciesToWispInTx(ctx context.Context, tx DBTX, id string) error {
	for _, table := range []string{"dependencies", "wisp_dependencies"} {
		if err := checkRetargetTargetCollision(ctx, tx, table, "depends_on_issue_id", "depends_on_wisp_id", id); err != nil {
			return err
		}
		if err := checkRenameTargetCollision(ctx, tx, table, "depends_on_wisp_id", id); err != nil {
			return err
		}
		if _, err := tx.ExecContext(ctx, fmt.Sprintf(`
			UPDATE %s
			SET depends_on_wisp_id = ?, depends_on_issue_id = NULL
			WHERE depends_on_issue_id = ?
		`, table), id, id); err != nil {
			return fmt.Errorf("retarget inbound dependencies to wisp in %s for %s: %w", table, id, err)
		}
	}
	return nil
}

func RetargetInboundDependenciesToIssueInTx(ctx context.Context, tx DBTX, id string) error {
	for _, table := range []string{"dependencies", "wisp_dependencies"} {
		if err := checkRetargetTargetCollision(ctx, tx, table, "depends_on_wisp_id", "depends_on_issue_id", id); err != nil {
			return err
		}
		if err := checkRenameTargetCollision(ctx, tx, table, "depends_on_issue_id", id); err != nil {
			return err
		}
		if _, err := tx.ExecContext(ctx, fmt.Sprintf(`
			UPDATE %s
			SET depends_on_issue_id = ?, depends_on_wisp_id = NULL
			WHERE depends_on_wisp_id = ?
		`, table), id, id); err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped driver error (%w) to identify the concrete cause (missing column, constraint, lock, IO).
  2. Run the project's schema migration (bd migrate/upgrade or equivalent) so dependencies and wisp_dependencies have depends_on_wisp_id.
  3. Retry the move once the database is reachable and no other writer holds the lock; the caller's transaction rolls back so state is consistent.
  4. If a constraint rejects the rewrite, remove or resolve the conflicting dependency edge first, then retry the move.

Example fix

// before: move fails against an unmigrated DB
err := MoveIssuePersistenceInTx(ctx, tx, issueID)
// after: ensure schema is current before moving
if err := store.Migrate(ctx); err != nil { return err }
err := MoveIssuePersistenceInTx(ctx, tx, issueID)
Defensive patterns

Strategy: try-catch

Validate before calling

// before moving issue->wisp, ensure schema supports typed targets
var col string
err := db.QueryRow(`SELECT depends_on_wisp_id FROM wisp_dependencies LIMIT 1`).Err()
if err != nil { return fmt.Errorf("run migrations first: %w", err) }

Type guard

func isRetargetErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "retarget inbound dependencies to wisp")
}

Try / catch

err := store.MoveIssue(ctx, issueID)
if err != nil {
    var DriverErr *store.DBError
    if errors.As(err, &DriverErr) && isTransient(DriverErr.Unwrap()) {
        err = retryTx(store.MoveIssue, issueID)
    }
    return err
}

Prevention

When it happens

Trigger: Calling MoveIssuePersistenceInTx to convert an issue into a wisp when the UPDATE on 'dependencies' or 'wisp_dependencies' fails — e.g. the database connection drops mid-transaction, a schema version predates the depends_on_wisp_id column, a CHECK/FK constraint rejects the rewrite, or the transaction was already aborted by an earlier error.

Common situations: Running bd against an old database that has not been migrated to the wisp/dual-target dependency schema; disk-full or locked SQLite/Dolt database during a move; a manually edited schema missing depends_on_wisp_id; concurrent writers causing lock timeouts mid-move.

Related errors


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