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
- Read the table name in the message to identify which rewrite failed, then fix the underlying SQL error
- Retry the rename; the tx rolls back so old/new IDs stay consistent
- Confirm both tables have the split depends_on_issue_id/depends_on_wisp_id columns (run pending migrations)
- 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
- Keep migrations current — the rewrite assumes split target columns exist
- Serialize issue renames against dependency writers
- Retry whole rename transactions on transient/lock errors
- Remember the FK cascade plus reinsert pairing; never patch only one side
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
- update wisp %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/812a28f508a298dc.
Report an issue: GitHub.