{"record":{"id":"5dfe7bd199000b1a","repo":"plandex-ai/plandex","slug":"error-updating-user-num-non-draft-plans-v","errorCode":null,"errorMessage":"error updating user num_non_draft_plans: %v","messagePattern":"error updating user num_non_draft_plans: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":303,"sourceCode":"\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\n\terr := os.MkdirAll(descriptionsDir, os.ModePerm)\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating convo message descriptions dir: %v\", err)\n\t}\n\n\tfor _, op := range description.Operations {\n\t\tif op.Content != \"\" {\n\t\t\tquoted := strconv.Quote(op.Content)\n\t\t\top.Content = quoted[1 : len(quoted)-1]","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L285-L321","documentation":"This error wraps a failure to increment the num_non_draft_plans counter for a user inside a transaction. The UPDATE statement on the users table failed, so the transaction caller receives a wrapped sqlx/driver error. It indicates the counter could not be kept in sync when a plan transitions out of draft status.","triggerScenarios":"tx.Exec(\"UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1\", userId) fails: connection dropped mid-transaction, transaction already aborted by a prior error, invalid user id (no row matched, not an error but worth noting), or Postgres error (lock timeout, dead connection).","commonSituations":"Long-lived transactions that exceed statement_timeout; a tx passed in after a previous statement failed (Postgres aborts the whole transaction); DB restart or failover during plan creation; passing an empty or stale userId from a deleted user row.","solutions":["Check the wrapped %v detail for the underlying Postgres error code and fix the root cause","Ensure the caller did not ignore an earlier error on the same tx (Postgres aborts subsequent statements after one fails)","Verify the users row exists for the userId being incremented","Add connection-pool health checks and reasonable statement_timeout settings","Retry the transaction at the caller level on transient connection errors"],"exampleFix":"// before\n_, err := tx.Exec(\"UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1\", userId)\nif err != nil {\n    return fmt.Errorf(\"error updating user num_non_draft_plans: %v\", err)\n}\n// after\nres, err := tx.Exec(\"UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1\", userId)\nif err != nil {\n    return fmt.Errorf(\"error updating user num_non_draft_plans: %w\", err)\n}\nif n, _ := res.RowsAffected(); n == 0 {\n    return fmt.Errorf(\"user %s not found\", userId)\n}","handlingStrategy":"try-catch","validationCode":"var exists bool\nerr := db.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)\", userId)\nif err != nil || !exists {\n    return fmt.Errorf(\"user %s not found, skipping increment\", userId)\n}","typeGuard":null,"tryCatchPattern":"if err := IncNumNonDraftPlans(userId, tx); err != nil {\n    var pqErr *pq.Error\n    if errors.As(err, &pqErr) && pqErr.Code == \"25P02\" {\n        // transaction aborted by earlier error; fix caller error handling\n    }\n    return err\n}","preventionTips":["Always check every error on a tx before issuing further statements","Validate userId exists before incrementing","Set sane statement_timeout and pool health checks","Wrap errors with %w so callers can errors.As the driver error"],"tags":["database","postgres","transaction","sql"],"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-15T02:17:10.978Z"}