{"record":{"id":"bc48aa9a0ee74169","repo":"plandex-ai/plandex","slug":"error-incrementing-num-non-draft-plans-v","errorCode":null,"errorMessage":"error incrementing num non draft plans: %v","messagePattern":"error incrementing num non draft plans: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/model/plan/tell_load.go","lineNumber":125,"sourceCode":"\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error generating plan name: %v\\n\", err)\n\t\t\t\t\terrCh <- fmt.Errorf(\"error generating plan name: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\terr = db.WithTx(active.Ctx, \"rename plan\", func(tx *sqlx.Tx) error {\n\t\t\t\t\terr := db.RenamePlan(planId, name, tx)\n\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"Error renaming plan: %v\\n\", err)\n\t\t\t\t\t\treturn fmt.Errorf(\"error renaming plan: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\terr = db.IncNumNonDraftPlans(currentUserId, tx)\n\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"Error incrementing num non draft plans: %v\\n\", err)\n\t\t\t\t\t\treturn fmt.Errorf(\"error incrementing num non draft plans: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error renaming plan: %v\\n\", err)\n\t\t\t\t\terrCh <- fmt.Errorf(\"error renaming plan: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terrCh <- nil\n\t\t}()\n\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/tell_load.go#L107-L143","documentation":"This error wraps a failure from db.IncNumNonDraftPlans, the second statement in the same 'rename plan' transaction, which increments the user's count of non-draft plans after a successful rename. If this UPDATE fails the transaction is rolled back (the rename is undone too) and the wrapped error is returned. It indicates the bookkeeping counter could not be updated even though the rename itself may have succeeded inside the tx.","triggerScenarios":"After a successful db.RenamePlan inside db.WithTx, db.IncNumNonDraftPlans(currentUserId, tx) fails — typically a connection loss between statements, a constraint violation or missing row for currentUserId in the user-stats table, or SQL mismatch after schema changes.","commonSituations":"User row/statistics table missing or migrated (column renamed), DB connection pool exhausted mid-transaction, deadlock with another transaction touching the same user row, statement timeout on a busy table.","solutions":["Inspect the wrapped inner error and 'Error incrementing num non draft plans' log to find the root SQL cause","Retry the request — the transaction rolled back so the draft is intact and the whole rename can be redone","Verify the user-stats row for currentUserId exists and the IncNumNonDraftPlans SQL matches the current schema","Check DB logs for deadlocks or lock waits on the user row during the transaction","Wrap the flow with a bounded retry for transient connection/deadlock errors"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// before the transaction\nvar exists bool\nerr := db.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)\", currentUserId)\nif err != nil || !exists {\n    return fmt.Errorf(\"user %s not found; cannot increment non-draft plan count\", currentUserId)\n}","typeGuard":"func incErrIsRollbackSafe(err error) bool {\n    // WithTx rolls back on any returned error; classify for retry decisions\n    var pgErr *pgconn.PgError\n    return errors.As(err, &pgErr) && (pgErr.Code == \"40001\" || pgErr.Code == \"40P01\")\n}","tryCatchPattern":"err = db.WithTx(active.Ctx, \"rename plan\", func(tx *sqlx.Tx) error {\n    if err := db.RenamePlan(planId, name, tx); err != nil {\n        return err\n    }\n    if err := db.IncNumNonDraftPlans(currentUserId, tx); err != nil {\n        return fmt.Errorf(\"error incrementing num non draft plans: %w\", err)\n    }\n    return nil\n})\nif err != nil {\n    log.Printf(\"rename tx rolled back: %v\", err)\n    return err\n}","preventionTips":["Ensure the user statistics row exists (upsert on user creation) so the UPDATE always affects a row","Keep IncNumNonDraftPlans SQL in sync with schema migrations","Keep transactions short to reduce deadlock risk on the user row","Use bounded retries for serialization-failure (40001) and deadlock (40P01) errors","Rely on WithTx rollback so a failed increment never leaves a half-renamed plan"],"tags":["database","transaction","counter","rollback"],"backgroundTag":"database-transaction-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}