gastownhall/beads · error
update wisp %s -> %s in %s: %w
Error message
update wisp %s -> %s in %s: %w
What it means
Wraps a failed target rewrite (replaceDependencyTargetInTx) when a wisp ID is renamed: UpdateWispIDInDependenciesInTx reinserts matching rows in dependencies and wisp_dependencies because Dolt can leave the generated depends_on_id column stale after an FK cascade. A failure in either table aborts the rename transaction with this error.
Source
Thrown at internal/storage/issueops/dependencies.go:606
if len(wispIDs) == 0 {
return nil
}
inClause, args := buildSQLInClause(wispIDs)
if _, err := tx.ExecContext(ctx,
fmt.Sprintf("DELETE FROM dependencies WHERE depends_on_wisp_id IN (%s)", inClause),
args...); err != nil {
return fmt.Errorf("delete wisps from dependencies: %w", err)
}
return nil
}
// Dependency target rewrites reinsert matching rows because Dolt can leave the
// stored generated depends_on_id column stale after a split target column is
// updated by FK cascade.
func UpdateWispIDInDependenciesInTx(ctx context.Context, tx *sql.Tx, oldID, newID string) error {
for _, table := range []string{"dependencies", "wisp_dependencies"} {
if err := replaceDependencyTargetInTx(ctx, tx, table, "depends_on_wisp_id", oldID, newID); err != nil {
return fmt.Errorf("update wisp %s -> %s in %s: %w", oldID, newID, table, err)
}
}
return nil
}
func UpdateIssueIDInDependenciesInTx(ctx context.Context, tx *sql.Tx, oldID, newID string) error {
for _, table := range []string{"dependencies", "wisp_dependencies"} {
if err := replaceDependencyTargetInTx(ctx, tx, table, "depends_on_issue_id", oldID, newID); err != nil {
return fmt.Errorf("update issue target %s -> %s in %s: %w", oldID, newID, table, err)
}
}
// Re-derive the deterministic primary key for rows whose SOURCE issue was
// renamed. dependencies.issue_id carries fk_dep_issue ... ON UPDATE CASCADE, so
// renaming the issues row (updateIssueIDInTx updates issues.id first) cascades
// issue_id from oldID to newID before we get here — but the cascade leaves the
// surrogate id at depid.New(oldID, target). A stale id re-forks the primary key
// across clones (#4259) and breaks the same-PK => same-edge invariant the pull
// conflict resolver relies on, so recompute it from the post-rename (newID, target).View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped table name in the message to see which table's rewrite failed and address the underlying SQL error
- Retry the rename; the transaction rolls back to the old ID consistently
- Verify both dependencies and wisp_dependencies exist and share the expected schema (migrations up to date)
- Ensure no concurrent writers hold locks on the dependency tables during the rename
Example fix
// before
if err := issueops.UpdateWispIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
return fmt.Errorf("rename wisp: %w", err)
}
// after
if err := issueops.UpdateWispIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
if strings.Contains(err.Error(), "wisp_dependencies") {
// run pending migrations / repair table before retrying
return fmt.Errorf("rename wisp: repair wisp_dependencies then retry: %w", err)
}
return fmt.Errorf("rename wisp: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify both target tables exist with expected schema before renaming
for _, table := range []string{"dependencies", "wisp_dependencies"} {
if !tableExists(ctx, db, table) || !columnExists(ctx, db, table, "depends_on_wisp_id") {
return fmt.Errorf("schema not ready for rename: %s", table)
}
} Type guard
func isWispRenameFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "update wisp ") && strings.Contains(err.Error(), "in dependencies")
} Try / catch
err := issueops.UpdateWispIDInDependenciesInTx(ctx, tx, oldID, newID)
if err != nil {
if isRetryableDriverErr(err) {
return retryWithBackoff(func() error { return renameWisp(ctx, oldID, newID) })
}
return err
} Prevention
- Run all migrations so both dependency tables exist with split target columns
- Avoid concurrent writers during renames to prevent lock failures
- Retry whole rename transactions on transient errors
- Understand the reinsert path exists for Dolt stale generated columns — don't bypass it
When it happens
Trigger: Renaming a wisp (updateWispIDInTx) when the rewrite of depends_on_wisp_id in dependencies or wisp_dependencies fails — SQL error, connection loss, or FK constraints from rows referencing the old ID in unexpected states.
Common situations: Renames during active sync with concurrent readers; Dolt generated-column quirks requiring the reinsert path; schema drift between environments making one of the two tables unavailable.
Related errors
- update issue target %s -> %s in %s: %w
- capture dependency edges for rename %s -> %s: %w
- update issue ID: %w
- issue not found: %s
- rename lease row: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c31878b0e180232e.
Report an issue: GitHub.