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

  1. Inspect the wrapped table name in the message to see which table's rewrite failed and address the underlying SQL error
  2. Retry the rename; the transaction rolls back to the old ID consistently
  3. Verify both dependencies and wisp_dependencies exist and share the expected schema (migrations up to date)
  4. 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

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


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