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
- Read the wrapped driver error (%w) to identify the concrete cause (missing column, constraint, lock, IO).
- Run the project's schema migration (bd migrate/upgrade or equivalent) so dependencies and wisp_dependencies have depends_on_wisp_id.
- 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.
- 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
- Run bd migrations as part of every deploy before issuing move/promote commands.
- Keep transactions short to avoid lock contention during moves.
- Retry whole move transactions on transient driver errors — they are atomic and safe to rerun.
- Monitor disk space and DB health before batch issue conversions.
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
- resolve existing wisps for batch delete: %w
- count wisp dependencies: %w
- count labels: %w
- count wisp labels: %w
- count events: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cc92a685ca121148.
Report an issue: GitHub.