{"record":{"id":"6245b170e1c6eb35","repo":"gastownhall/beads","slug":"retarget-inbound-dependencies-to-issue-in-s-for","errorCode":null,"errorMessage":"retarget inbound dependencies to issue in %s for %s: %w","messagePattern":"retarget inbound dependencies to issue in (.+?) for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":813,"sourceCode":"\t\t}\n\t}\n\treturn nil\n}\n\nfunc RetargetInboundDependenciesToIssueInTx(ctx context.Context, tx DBTX, id string) error {\n\tfor _, table := range []string{\"dependencies\", \"wisp_dependencies\"} {\n\t\tif err := checkRetargetTargetCollision(ctx, tx, table, \"depends_on_wisp_id\", \"depends_on_issue_id\", id); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := checkRenameTargetCollision(ctx, tx, table, \"depends_on_issue_id\", id); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := tx.ExecContext(ctx, fmt.Sprintf(`\n\t\t\tUPDATE %s\n\t\t\tSET depends_on_issue_id = ?, depends_on_wisp_id = NULL\n\t\t\tWHERE depends_on_wisp_id = ?\n\t\t`, table), id, id); err != nil {\n\t\t\treturn fmt.Errorf(\"retarget inbound dependencies to issue in %s for %s: %w\", table, id, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// UpdateIssueIDInDependencyTargetsInTx is called after the issues PK is updated\n// from oldID to newID. FK ON UPDATE CASCADE has already propagated\n// depends_on_issue_id from oldID to newID across dependencies and\n// wisp_dependencies, so no rewrite is needed.\nfunc UpdateIssueIDInDependencyTargetsInTx(ctx context.Context, tx *sql.Tx, _, newID string) error {\n\tfor _, table := range []string{\"dependencies\", \"wisp_dependencies\"} {\n\t\tif err := checkRenameTargetCollision(ctx, tx, table, \"depends_on_issue_id\", newID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":795,"sourceCodeEnd":831,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L795-L831","documentation":"This error wraps a SQL failure from the UPDATE that rewrites inbound dependency edges pointing at a wisp (depends_on_wisp_id = id) so they point at a regular issue (depends_on_issue_id = id) during a wisp-to-issue promotion. It is thrown by RetargetInboundDependenciesToIssueInTx, which runs the UPDATE on both dependencies and wisp_dependencies tables within the caller's transaction. The wrapped error is the underlying driver error.","triggerScenarios":"Calling MoveIssuePersistenceInTx (wisp rename/move) or PromoteFromEphemeralInTx when the UPDATE statement fails — missing depends_on_issue_id column in an unmigrated schema, connection loss, constraint rejection, or an already-aborted transaction.","commonSituations":"Promoting an ephemeral/wisp issue to a permanent issue on an out-of-date database schema; a crashed or locked Dolt/SQLite backend during promotion; partially applied migrations that created wisp_dependencies but not the typed issue-target column.","solutions":["Inspect the wrapped driver error to determine the root cause.","Apply pending schema migrations so both dependency tables support depends_on_issue_id.","Retry the promotion after resolving lock/availability issues; the transaction rollback leaves wisps intact.","If constraints block the rewrite, delete or remap the offending dependency rows first, then rerun."],"exampleFix":"// before: promotion fails with 'no such column: depends_on_issue_id'\nerr := PromoteFromEphemeralInTx(ctx, tx, wispID)\n// after: check schema version first\nif !store.SchemaSupportsTypedDependencyTargets() { return fmt.Errorf(\"run bd migrate first\") }\nerr := PromoteFromEphemeralInTx(ctx, tx, wispID)","handlingStrategy":"try-catch","validationCode":"// verify both dependency tables can target issues before promoting a wisp\nfor _, t := range []string{\"dependencies\", \"wisp_dependencies\"} {\n    if err := db.QueryRow(fmt.Sprintf(\"SELECT depends_on_issue_id FROM %s LIMIT 1\", t)).Err(); err != nil {\n        return fmt.Errorf(\"%s missing typed column; migrate first: %w\", t, err)\n    }\n}","typeGuard":"func isRetargetToIssueErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"retarget inbound dependencies to issue\")\n}","tryCatchPattern":"err := store.PromoteFromEphemeral(ctx, wispID)\nif err != nil {\n    if isTransientDBError(errors.Unwrap(err)) {\n        return retryTx(func(tx *sql.Tx) error { return store.PromoteFromEphemeralInTx(ctx, tx, wispID) })\n    }\n    return err\n}","preventionTips":["Never promote wisps against a database with pending migrations.","Retry promotion transactions wholesale on transient errors; rollback leaves the wisp intact.","Check server/DB availability before batch ephemeral promotion sweeps.","Pin schema version checks into your deployment health checks."],"tags":["storage","sql","dependencies","migration"],"backgroundTag":"dependency-retarget-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}