{"record":{"id":"308bf94f3b880cde","repo":"gastownhall/beads","slug":"rename-to-s-collides-with-existing-dependency-tar","errorCode":null,"errorMessage":"rename to %s collides with existing dependency target in %s","messagePattern":"rename to (.+?) collides with existing dependency target in (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":900,"sourceCode":"\t\tSELECT 1 FROM %s a\n\t\tJOIN %s b ON a.issue_id = b.issue_id\n\t\tWHERE a.%s = ?\n\t\t  AND (b.%s = ? OR b.%s = ?)\n\t\tLIMIT 1\n\t`, table, table, typedCol, otherCols[0], otherCols[1])\n\n\tvar found int\n\terr := tx.QueryRowContext(ctx, query, newID, newID, newID).Scan(&found)\n\tif err == sql.ErrNoRows {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\tif isTableNotExistError(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"check rename collision in %s: %w\", table, err)\n\t}\n\treturn fmt.Errorf(\"rename to %s collides with existing dependency target in %s\", newID, table)\n}\n\n// RemoveDependencyInTx removes a dependency between two issues within an\n// existing transaction. Automatically routes to wisp_dependencies if the\n// source issue is an active wisp. When emitEvent is set and a row is actually\n// removed it records a dependency_removed event (attributed to actor) on the\n// source's event table; a no-op remove of a missing edge, or a structural remove\n// with emitEvent unset, records nothing. Only the explicit bd dep remove verb\n// sets emitEvent; structural removals (issue delete, reparent, batch, duplicate\n// cleanup) leave it unset so they wire edges away silently, mirroring the\n// proxied repository's DepInsertOpts.EmitEvent gate so both backends record\n// identical history.\n//\n// It returns whether a dependency_removed event was actually written, so callers\n// that stage tables for a Dolt commit stage the events table only when an event\n// row exists (avoiding the sweep-unrelated-rows hazard doltAddAndCommit guards\n// against, GH#2455).\n//","sourceCodeStart":882,"sourceCodeEnd":918,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L882-L918","documentation":"Deliberate domain error from checkRenameTargetCollision: renaming a dependency target to newID would leave some issue_id holding edges that point to newID through two different target representations at once (typed column being renamed plus the other typed column or the external column). The rename is aborted to prevent duplicate/conflicting dependency edges.","triggerScenarios":"replaceDependencyTargetInTx, the retarget helpers, or UpdateIssueIDInDependencyTargetsInTx (after an issues PK rename) finds a row pair where a.issue_id = b.issue_id, a.<typedCol> = newID, and (b.<otherTypedCol> = newID OR b.depends_on_external = newID) in dependencies or wisp_dependencies.","commonSituations":"Renaming an issue to an ID that another edge already references via an external/typed target on the same dependent issue; importing issues with IDs that collide with existing external dependency targets; legacy data with both a wisp edge and an issue edge for the same pair.","solutions":["Locate conflicts: SELECT a.issue_id FROM <table> a JOIN <table> b ON a.issue_id=b.issue_id WHERE a.<typedCol>='newID' AND (b.<other>='newID' OR b.depends_on_external='newID').","Remove or rewrite the shadow edge (keep exactly one target representation) before renaming.","Choose a different target ID if the collision stems from an ID naming clash.","Run consistency tooling (bd doctor) to clean duplicate dependency edges after fixing."],"exampleFix":"// before: rename bd-42 -> bd-99 collides\n// bd-7 has depends_on_issue_id='bd-42' AND depends_on_external='bd-99'\n// after: clear the stale external target first\nUPDATE dependencies SET depends_on_external=NULL WHERE issue_id='bd-7' AND depends_on_external='bd-99';\nerr := store.RenameIssue(ctx, \"bd-42\", \"bd-99\")","handlingStrategy":"validation","validationCode":"func renameCollides(ctx context.Context, db *sql.DB, table, typedCol, otherCol, newID string) (bool, error) {\n    q := \"SELECT 1 FROM \" + table + \" a JOIN \" + table + \" b ON a.issue_id=b.issue_id WHERE a.\" + typedCol +\n        \"=? AND (b.\" + otherCol + \"=? OR b.depends_on_external=?) LIMIT 1\"\n    var one int\n    err := db.QueryRowContext(ctx, q, newID, newID, newID).Scan(&one)\n    if errors.Is(err, sql.ErrNoRows) { return false, nil }\n    return err == nil, err\n}\n// run for dependencies/wisp_dependencies before renaming to newID","typeGuard":"func isRenameCollision(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"rename to\") && strings.Contains(err.Error(), \"collides with existing dependency target\")\n}","tryCatchPattern":"if err := store.RenameIssue(ctx, oldID, newID); isRenameCollision(err) {\n    return fmt.Errorf(\"pick a different ID or clear the shadow edge: %w\", err)\n}","preventionTips":["Before renaming an issue, search dependency tables for edges already targeting the new ID (typed or external).","Avoid issuing external dependency targets that mirror internal issue IDs.","Clean legacy dual-target rows with bd doctor before large rename batches.","Reserve/validate new IDs against a registry to avoid accidental collisions."],"tags":["dependencies","data-integrity","rename","collision"],"backgroundTag":"dependency-collision","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}