{"record":{"id":"1641d823013b2122","repo":"plandex-ai/plandex","slug":"error-renaming-plan-v","errorCode":null,"errorMessage":"error renaming plan: %v","messagePattern":"error renaming plan: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":283,"sourceCode":"\t_, err := Conn.Exec(\"UPDATE branches SET status = $1, error = $2 WHERE plan_id = $3 AND name = $4\", status, errStr, planId, branch)\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error setting plan status: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc RenamePlan(planId string, name string, tx *sqlx.Tx) error {\n\tvar err error\n\tif tx == nil {\n\t\t_, err = Conn.Exec(\"UPDATE plans SET name = $1 WHERE id = $2\", name, planId)\n\t} else {\n\t\t_, err = tx.Exec(\"UPDATE plans SET name = $1 WHERE id = $2\", name, planId)\n\t}\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error renaming plan: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc IncActiveBranches(planId string, inc int, tx *sqlx.Tx) error {\n\t_, err := tx.Exec(\"UPDATE plans SET active_branches = active_branches + $1 WHERE id = $2\", inc, planId)\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating plan active branches: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc IncNumNonDraftPlans(userId string, tx *sqlx.Tx) error {\n\t_, err := tx.Exec(\"UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1\", userId)\n","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L265-L301","documentation":"RenamePlan updates the plans.name column, executing against either the supplied transaction (tx != nil) or the global Conn. This wrapper fires when the UPDATE fails. Because the function accepts an optional *sqlx.Tx, an error can also indicate the surrounding transaction was already aborted/rolled back, making every statement on it fail.","triggerScenarios":"Renaming a plan with a nil tx (uses Conn) or a live tx: DB connection failure, transaction already aborted, name violating a UNIQUE constraint on plans.name, or planId referencing a deleted plan (silent no-op unless a constraint fires).","commonSituations":"Renaming to a name that already exists when plans.name is unique; calling RenamePlan inside a transaction that a previous step already failed and rolled back; DB migration renamed the name column or added length limits exceeded by the new name.","solutions":["Check the inner error for unique-constraint violations and surface a 'name already taken' message to the user","If a tx was passed, verify the transaction is still valid/committed — an aborted tx makes this UPDATE fail; roll back and retry the whole operation","Validate the new name (length, allowed characters) against schema constraints before calling RenamePlan","Confirm the plans row exists (GetPlan) before renaming to distinguish no-op from failure"],"exampleFix":"// before\n_, err = tx.Exec(\"UPDATE plans SET name = $1 WHERE id = $2\", name, planId)\nif err != nil {\n    return fmt.Errorf(\"error renaming plan: %v\", err)\n}\n// after\n_, err = tx.Exec(\"UPDATE plans SET name = $1 WHERE id = $2\", name, planId)\nif err != nil {\n    var pgErr *pgconn.PgError\n    if errors.As(err, &pgErr) && pgErr.Code == \"23505\" {\n        return ErrPlanNameTaken\n    }\n    return fmt.Errorf(\"error renaming plan: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"var exists bool\nif err := Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)\", planId); err != nil || !exists {\n    return fmt.Errorf(\"plan %s not found\", planId)\n}\nif name == \"\" || len(name) > 255 {\n    return fmt.Errorf(\"invalid plan name\")\n}","typeGuard":"func canRename(planId, name string) bool {\n    var exists bool\n    _ = Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)\", planId)\n    return exists && name != \"\"\n}","tryCatchPattern":"if err := RenamePlan(planId, name, tx); err != nil {\n    if strings.Contains(err.Error(), \"duplicate key\") || strings.Contains(err.Error(), \"23505\") {\n        return fmt.Errorf(\"a plan named %q already exists\", name)\n    }\n    tx.Rollback()\n    return err\n}","preventionTips":["Check for unique-constraint violations (23505) and map them to user-facing 'name taken' errors","Ensure failed tx steps roll back before later statements run","Validate name length/characters against schema limits before renaming","Confirm the plan exists before entering the rename transaction"],"tags":["database","postgresql","go","transactions"],"backgroundTag":"database-update-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}