gastownhall/beads · error

update issue target %s -> %s in %s: %w

Error message

update issue target %s -> %s in %s: %w

What it means

Wraps a failed rewrite of depends_on_issue_id in dependencies or wisp_dependencies when an issue ID is renamed. Because the FK cascade updates dependencies.issue_id but can leave the generated depends_on_id column stale in Dolt, replaceDependencyTargetInTx reinserts the rows; if that fails for either table the rename transaction is aborted with this error.

Source

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

	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).
	if err := rekeyDependencySourceInTx(ctx, tx, oldID, newID); err != nil {
		return fmt.Errorf("rekey dependency sources %s -> %s: %w", oldID, newID, err)
	}
	return nil
}

// rekeyDependencySourceInTx rewrites dependencies.id for every edge whose source
// issue was renamed to newID so the stored id equals depid.New(newID, target). It
// matches rows by both newID (the normal post-FK-cascade state) and oldID (defensive,

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the table name in the message to identify which rewrite failed, then fix the underlying SQL error
  2. Retry the rename; the tx rolls back so old/new IDs stay consistent
  3. Confirm both tables have the split depends_on_issue_id/depends_on_wisp_id columns (run pending migrations)
  4. Serialize renames against other dependency writers to avoid lock contention

Example fix

// before
if err := issueops.UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
	return fmt.Errorf("rename issue: %w", err)
}
// after
if err := issueops.UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
	return fmt.Errorf("rename issue %s -> %s: %w", oldID, newID, err)
} // caller retries with backoff on transient/lock errors
Defensive patterns

Strategy: retry

Validate before calling

// confirm schema readiness and no active lock before renaming an issue
if !columnExists(ctx, db, "dependencies", "depends_on_issue_id") {
	return errors.New("run migrations before issue rename")
}
if err := db.PingContext(ctx); err != nil {
	return fmt.Errorf("db unreachable, defer rename of %s", oldID)
}

Type guard

func isIssueRenameFailure(err error) bool {
	return err != nil && strings.Contains(err.Error(), "update issue target ") && strings.Contains(err.Error(), "in dependencies")
}

Try / catch

err := issueops.UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID)
if err != nil {
	if isRetryableDriverErr(err) {
		return retryWithBackoff(func() error { return renameIssue(ctx, oldID, newID) })
	}
	return err
}

Prevention

When it happens

Trigger: Renaming an issue (updateIssueIDInTx) where the depends_on_issue_id rewrite errors in either table — SQL failure, connection loss, or stale generated-column rows conflicting with the reinsert.

Common situations: Issue renames under concurrent dependency reads/writes during sync; environments missing recent migrations for the split target columns; large dependency fan-out making the rewrite slow enough to hit lock timeouts.

Related errors


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