{"record":{"id":"6fdbf466e54592e2","repo":"plandex-ai/plandex","slug":"error-updating-plan-active-branches-v","errorCode":null,"errorMessage":"error updating plan active branches: %v","messagePattern":"error updating plan active branches: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":293,"sourceCode":"\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\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating user num_non_draft_plans: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc StoreDescription(description *ConvoMessageDescription) error {\n\tdescriptionsDir := getPlanDescriptionsDir(description.OrgId, description.PlanId)\n","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L275-L311","documentation":"IncActiveBranches increments (or decrements, via a negative inc) plans.active_branches using the required *sqlx.Tx. This wrapper fires when that UPDATE fails. Since it always runs inside a transaction (called by CreateBranch and transactional anonymous blocks), the most common cause is executing against a transaction that was already aborted, or a connection failure during commit-heavy operations.","triggerScenarios":"Calling IncActiveBranches with an already-rolled-back or committed tx; DB connection failure inside the transaction; concurrent branch creation causing lock contention; constraint/trigger failure on the plans table during the increment.","commonSituations":"CreateBranch flows where a prior statement in the tx failed and the tx was not rolled back before this call; pool exhaustion while holding a tx open too long; passing a negative inc that drives active_branches negative when the column has a CHECK constraint.","solutions":["Ensure every error in the surrounding transaction triggers tx.Rollback() before any further statement — an aborted tx makes this UPDATE fail","Check the inner error for connection issues; if the pool is exhausted, close transactions faster (avoid holding them across I/O)","Verify inc values keep active_branches >= 0 if the column has a non-negative CHECK constraint","Confirm the plans row for planId exists inside the tx before incrementing"],"exampleFix":"// before\n_, err := tx.Exec(\"UPDATE plans SET active_branches = active_branches + $1 WHERE id = $2\", inc, planId)\nif err != nil {\n    return fmt.Errorf(\"error updating plan active branches: %v\", err)\n}\n// after\n_, err := tx.Exec(\"UPDATE plans SET active_branches = active_branches + $1 WHERE id = $2\", inc, planId)\nif err != nil {\n    tx.Rollback()\n    return fmt.Errorf(\"error updating plan active branches: %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}","typeGuard":"func txUsable(tx *sqlx.Tx) bool {\n    return tx != nil\n}","tryCatchPattern":"err := IncActiveBranches(planId, 1, tx)\nif err != nil {\n    _ = tx.Rollback()\n    return fmt.Errorf(\"branch creation failed: %w\", err)\n}\nif err := tx.Commit(); err != nil {\n    return fmt.Errorf(\"commit failed: %w\", err)\n}","preventionTips":["Roll back the transaction immediately on any error so subsequent statements never run on an aborted tx","Do not hold transactions open across slow external I/O (avoids pool exhaustion and lock timeouts)","Ensure inc values (positive or negative) keep active_branches valid under column constraints","Keep the plan row's existence guaranteed within the same transaction that creates branches"],"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"}