{"record":{"id":"812a28f508a298dc","repo":"gastownhall/beads","slug":"update-issue-target-s-s-in-s-w","errorCode":null,"errorMessage":"update issue target %s -> %s in %s: %w","messagePattern":"update issue target (.+?) -> (.+?) in (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":615,"sourceCode":"\treturn nil\n}\n\n// Dependency target rewrites reinsert matching rows because Dolt can leave the\n// stored generated depends_on_id column stale after a split target column is\n// updated by FK cascade.\nfunc UpdateWispIDInDependenciesInTx(ctx context.Context, tx *sql.Tx, oldID, newID string) error {\n\tfor _, table := range []string{\"dependencies\", \"wisp_dependencies\"} {\n\t\tif err := replaceDependencyTargetInTx(ctx, tx, table, \"depends_on_wisp_id\", oldID, newID); err != nil {\n\t\t\treturn fmt.Errorf(\"update wisp %s -> %s in %s: %w\", oldID, newID, table, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc UpdateIssueIDInDependenciesInTx(ctx context.Context, tx *sql.Tx, oldID, newID string) error {\n\tfor _, table := range []string{\"dependencies\", \"wisp_dependencies\"} {\n\t\tif err := replaceDependencyTargetInTx(ctx, tx, table, \"depends_on_issue_id\", oldID, newID); err != nil {\n\t\t\treturn fmt.Errorf(\"update issue target %s -> %s in %s: %w\", oldID, newID, table, err)\n\t\t}\n\t}\n\t// Re-derive the deterministic primary key for rows whose SOURCE issue was\n\t// renamed. dependencies.issue_id carries fk_dep_issue ... ON UPDATE CASCADE, so\n\t// renaming the issues row (updateIssueIDInTx updates issues.id first) cascades\n\t// issue_id from oldID to newID before we get here — but the cascade leaves the\n\t// surrogate id at depid.New(oldID, target). A stale id re-forks the primary key\n\t// across clones (#4259) and breaks the same-PK => same-edge invariant the pull\n\t// conflict resolver relies on, so recompute it from the post-rename (newID, target).\n\tif err := rekeyDependencySourceInTx(ctx, tx, oldID, newID); err != nil {\n\t\treturn fmt.Errorf(\"rekey dependency sources %s -> %s: %w\", oldID, newID, err)\n\t}\n\treturn nil\n}\n\n// rekeyDependencySourceInTx rewrites dependencies.id for every edge whose source\n// issue was renamed to newID so the stored id equals depid.New(newID, target). It\n// matches rows by both newID (the normal post-FK-cascade state) and oldID (defensive,","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L597-L633","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif err := issueops.UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {\n\treturn fmt.Errorf(\"rename issue: %w\", err)\n}\n// after\nif err := issueops.UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {\n\treturn fmt.Errorf(\"rename issue %s -> %s: %w\", oldID, newID, err)\n} // caller retries with backoff on transient/lock errors","handlingStrategy":"retry","validationCode":"// confirm schema readiness and no active lock before renaming an issue\nif !columnExists(ctx, db, \"dependencies\", \"depends_on_issue_id\") {\n\treturn errors.New(\"run migrations before issue rename\")\n}\nif err := db.PingContext(ctx); err != nil {\n\treturn fmt.Errorf(\"db unreachable, defer rename of %s\", oldID)\n}","typeGuard":"func isIssueRenameFailure(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"update issue target \") && strings.Contains(err.Error(), \"in dependencies\")\n}","tryCatchPattern":"err := issueops.UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID)\nif err != nil {\n\tif isRetryableDriverErr(err) {\n\t\treturn retryWithBackoff(func() error { return renameIssue(ctx, oldID, newID) })\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["database","rename","foreign-keys"],"backgroundTag":"dependency-target-rewrite-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}