{"record":{"id":"8e36861c664ce2b7","repo":"plandex-ai/plandex","slug":"error-updating-plan-total-replies-v","errorCode":null,"errorMessage":"error updating plan total replies: %v","messagePattern":"error updating plan total replies: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":179,"sourceCode":"\t\terrCh <- nil\n\t}()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlog.Printf(\"panic in AddPlanConvoMessage: %v\\n%s\", r, debug.Stack())\n\t\t\t\terrCh <- fmt.Errorf(\"panic in AddPlanConvoMessage: %v\\n%s\", r, debug.Stack())\n\t\t\t\truntime.Goexit() // don't allow outer function to continue and double-send to channel\n\t\t\t}\n\t\t}()\n\n\t\tif msg.Role != openai.ChatMessageRoleAssistant {\n\t\t\terrCh <- nil\n\t\t\treturn\n\t\t}\n\t\t_, err := Conn.Exec(\"UPDATE plans SET total_replies = total_replies + 1 WHERE id = $1\", msg.PlanId)\n\t\tif err != nil {\n\t\t\terrCh <- fmt.Errorf(\"error updating plan total replies: %v\", err)\n\t\t}\n\n\t\terrCh <- nil\n\t}()\n\n\tfor i := 0; i < 2; i++ {\n\t\terr := <-errCh\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error updating plan tokens: %v\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc SyncPlanTokens(orgId, planId, branch string) error {\n\tvar contexts []*Context\n\tvar convos []*ConvoMessage","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L161-L197","documentation":"This error is produced inside the AddPlanConvoMessage helper's goroutine when the SQL UPDATE that increments a plan's total_replies counter fails. After an assistant message is stored, the code bumps total_replies on the plans row matching msg.PlanId; a database-level failure (connection issue, bad plan id, schema problem) surfaces here wrapped as 'error updating plan total replies'. The wrapped error is then re-wrapped by the caller's collector as 'error updating plan tokens'.","triggerScenarios":"An assistant-role ChatMessage is stored and `UPDATE plans SET total_replies = total_replies + 1 WHERE id = $1` returns an error: DB connection dropped/pool exhausted, msg.PlanId references a deleted plan, wrong planId format, or a table/column mismatch after migration.","commonSituations":"Postgres restart or connection timeout mid-stream; plan deleted concurrently while a reply is still streaming in; stale PlanId from client after branch/plan removal; migrations that renamed total_replies or the plans table.","solutions":["Check the inner error (the %v payload) for connection/refused errors and verify Postgres is reachable and the connection pool is configured for the request load","Verify msg.PlanId references an existing plans row before calling AddPlanConvoMessage (e.g. via GetPlan)","Confirm the plans table has the total_replies column (run the current migrations)","Retry AddPlanConvoMessage; the counter increment is idempotent-unsafe so only retry once the message insert is confirmed not duplicated"],"exampleFix":"// before\n_, err := Conn.Exec(\"UPDATE plans SET total_replies = total_replies + 1 WHERE id = $1\", msg.PlanId)\nif err != nil {\n    errCh <- fmt.Errorf(\"error updating plan total replies: %v\", err)\n}\n// after\nvar exists bool\nif err := Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)\", msg.PlanId); err == nil && exists {\n    _, err := Conn.Exec(\"UPDATE plans SET total_replies = total_replies + 1 WHERE id = $1\", msg.PlanId)\n    if err != nil {\n        errCh <- fmt.Errorf(\"error updating plan total replies: %v\", err)\n    }\n}\nerrCh <- nil","handlingStrategy":"try-catch","validationCode":"var exists bool\nif err := Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)\", msg.PlanId); err != nil || !exists {\n    return fmt.Errorf(\"plan %s does not exist\", msg.PlanId)\n}","typeGuard":"func planExists(planId string) bool {\n    var exists bool\n    _ = Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)\", planId)\n    return exists\n}","tryCatchPattern":"if err := AddPlanConvoMessage(planId, msg); err != nil {\n    log.Printf(\"plan token update failed for plan %s: %v\", planId, err)\n    // check errors.Is/sqlstate for transient errors and retry once\n}","preventionTips":["Validate msg.PlanId exists before storing the assistant message","Keep the DB connection pool sized for streaming load and set sensible timeouts","Run migrations before deploys so total_replies always exists","Monitor Postgres health and alert on connection errors"],"tags":["database","postgresql","go"],"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"}