Skip to content

fix(backend): sync tier on checkout + expire abandoned sessions + lazy Stripe reconciliation - #13060

Merged
majdyz merged 17 commits into
devfrom
fix/stripe-checkout-sync-tier
May 9, 2026
Merged

fix(backend): sync tier on checkout + expire abandoned sessions + lazy Stripe reconciliation#13060
majdyz merged 17 commits into
devfrom
fix/stripe-checkout-sync-tier

Conversation

@majdyz

@majdyz majdyz commented May 8, 2026

Copy link
Copy Markdown
Contributor

Why / What / How

Why: Two related production billing bugs found via real-money testing, with a third systemic fix added:

  1. A user who successfully purchased a Pro subscription ($60 charged, Stripe shows "Succeeded") was left on NO_TIER and hit the paywall. Root cause: prod webhook was only subscribed to checkout.session.completedcustomer.subscription.created was never delivered, so the tier update never fired.
  2. Starting a checkout then abandoning it left an open Stripe invoice on the billing page with "OPEN" status, as if the user owed money for a purchase they never completed.
  3. Even with the webhook fixed, a missed or late webhook had no recovery path — a user would stay on NO_TIER indefinitely.

What:

  • checkout.session.completed now immediately syncs the subscription tier via the new sync_tier_from_checkout_session helper, making tier activation robust to missing/delayed customer.subscription.created webhooks (fast path)
  • create_subscription_checkout now expires any open subscription sessions for the customer before creating a new one, clearing phantom open invoices
  • When get_user_tier returns NO_TIER (and the DB confirmed it, not a DB error), a lazy Stripe reconciliation check is attempted (at most once per 5 minutes per user via a Redis NX gate) — if an active subscription exists, the tier is synced immediately, recovering from any missed webhook (safety net)

How:

  • v1.py webhook handler: on checkout.session.completed, calls the new sync_tier_from_checkout_session helper. Failures propagate as 5xx so Stripe retries the webhook (matching the existing pattern for customer.subscription.* events). This relies on Stripe's at-least-once delivery + retry mechanism as the primary recovery path; lazy reconciliation is the secondary one.
  • credit.py:
    • sync_tier_from_checkout_session(data_object) — retrieves the Stripe subscription and calls sync_subscription_from_stripe; no-op for non-subscription modes or missing subscription ID.
    • _expire_open_subscription_sessions(customer_id) — async helper using native list_async/expire_async; paginates and expires open subscription sessions. Called at the start of create_subscription_checkout.
    • reconcile_stripe_tier_for_user(user_id) — looks up the active Stripe subscription for the customer and syncs the tier; returns True on success.
  • rate_limit.py: _maybe_reconcile_stripe_tier sets a Redis NX key (stripe_reconcile:{user_id}, 300s TTL) before calling reconcile_stripe_tier_for_user. On transient errors the key is deleted so the next request can retry. Reconciliation is only attempted when the DB confirmed the user is NO_TIER (a tier_from_db flag distinguishes this from a DB read failure). After a successful reconciliation, a DB re-read failure is logged and falls back to stale NO_TIER rather than propagating to the caller.

Changes 🏗️

  • backend/api/features/v1.py: Sync subscription tier on checkout.session.completed via new sync_tier_from_checkout_session helper; failures propagate as 5xx so Stripe retries
  • backend/data/credit.py: Add sync_tier_from_checkout_session, _expire_open_subscription_sessions, reconcile_stripe_tier_for_user; call expire helper at the start of create_subscription_checkout
  • backend/copilot/rate_limit.py: Add _maybe_reconcile_stripe_tier and lazy-reconcile call in get_user_tier when DB confirms NO_TIER; skip reconciliation when initial DB read failed
  • backend/api/features/v1_stripe_webhook_test.py: 12 unit tests covering webhook handler (including 5xx-on-failure assertion), session expiry (including pagination), sync_tier_from_checkout_session, and reconcile_stripe_tier_for_user (no DB required)

Checklist 📋

For code changes:

  • I have clearly listed my changes in the PR description
  • I have made a test plan
  • I have tested my changes according to the test plan:
    • 12 unit tests pass locally (poetry run pytest backend/api/features/v1_stripe_webhook_test.py)
    • Verified no regressions in existing test suite
    • E2E /pr-test confirmed: checkout tier sync + no-webhook reconciliation both PASS
    • Affected user manually unblocked in prod pending this deploy

…essions

- checkout.session.completed now retrieves the subscription and calls
  sync_subscription_from_stripe immediately, instead of depending solely
  on customer.subscription.created which may arrive late or not at all
- create_subscription_checkout now expires any open subscription checkout
  sessions before creating a new one, preventing phantom open invoices
  from abandoned carts showing on the billing page
@majdyz
majdyz requested a review from a team as a code owner May 8, 2026 16:24
@majdyz
majdyz requested review from kcze and ntindle and removed request for a team May 8, 2026 16:24
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban May 8, 2026
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

This PR targets the master branch but does not come from dev or a hotfix/* branch.

Automatically setting the base branch to dev.

@github-actions
github-actions Bot changed the base branch from master to dev May 8, 2026 16:24
@github-actions github-actions Bot added the platform/backend AutoGPT Platform - Back end label May 8, 2026
@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Expires any open Stripe "subscription" checkout sessions for a customer before creating a new subscription checkout. After a successful subscription-mode checkout webhook, retrieves the Stripe subscription and calls sync_subscription_from_stripe; Stripe errors are logged and do not break the webhook. Tests added for these behaviors.

Changes

Subscription Checkout Session Management

Layer / File(s) Summary
Session Expiration Helpers
autogpt_platform/backend/backend/data/credit.py
New sync helper paginates open checkout sessions for a customer, filters to mode == "subscription", and expires each, with per-session Stripe error logging; an async wrapper runs this in a threadpool and logs overall errors.
Checkout Session Cleanup Integration
autogpt_platform/backend/backend/data/credit.py
create_subscription_checkout() now calls _expire_open_subscription_sessions(customer_id) after resolving customer_id and before creating the Checkout session.
Webhook Subscription Sync
autogpt_platform/backend/backend/api/features/v1.py
After fulfilling a subscription-mode checkout, webhook retrieves the Stripe subscription (when ID present) and calls sync_subscription_from_stripe(); Stripe retrieval errors are logged and swallowed.
Lazy Reconcile Gate (rate_limit)
autogpt_platform/backend/backend/copilot/rate_limit.py
Adds a Redis-gated helper that limits reconcile_stripe_tier_for_user to once per user per 5 minutes and updates get_user_tier to attempt reconciliation when tier resolves to NO_TIER.
Tests and Verification
autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
FastAPI test app and helper to fabricate checkout events. Tests cover subscription-mode sync, payment-mode no-sync, threadpool StripeError resilience, listing open sessions (including pagination), expiring only subscription-mode sessions, and async reconcile behavior for user tiers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Pwuts
  • Bentlybro

Poem

🐰 I hopped through sessions, stale and new,
I nudged the old ones gently through,
When webhooks rang and IDs aligned,
I fetched the sub and synced its find,
A rabbit cheers — the tiers renew!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main changes: syncing tier on checkout, expiring abandoned sessions, and lazy Stripe reconciliation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing three billing bugs, the specific changes made to fix them, and how each change addresses the root causes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/stripe-checkout-sync-tier

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/l label May 8, 2026
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 0 conflict(s), 0 medium risk, 5 low risk (out of 5 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Comment thread autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py Outdated
Comment thread autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py`:
- Around line 132-169: Move the local imports of
_list_and_expire_open_subscription_sessions to a single top-level import at the
top of the test module and remove the in-function imports inside
test_expire_open_subscription_sessions_called_on_checkout and
test_expire_open_subscription_sessions_expires_subscription_sessions; ensure
both tests use the top-level _list_and_expire_open_subscription_sessions
reference and that any mocking (mocker.patch on
stripe.checkout.Session.list/expire) still happens inside each test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35d0b1ca-5aa4-4cc8-9b88-958352a1b9ee

📥 Commits

Reviewing files that changed from the base of the PR and between 16ceb09 and 8bf6b77.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
  • autogpt_platform/backend/backend/data/credit.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.11)
  • GitHub Check: Analyze (python)
  • GitHub Check: type-check (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: types
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/backend/backend/api/features/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Update routes in '/backend/backend/api/features/' and add/update Pydantic models in the same directory for API development

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/backend/**/api/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/api/**/*.py: Use Security() instead of Depends() for authentication dependencies to get proper OpenAPI security specification
Follow SSE (Server-Sent Events) protocol: use data: lines for frontend-parsed events (must match Zod schema) and : comment lines for heartbeats/status

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/backend/backend/data/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All data access in backend requires user ID checks; verify this for any 'data/*.py' changes

Files:

  • autogpt_platform/backend/backend/data/credit.py
autogpt_platform/**/data/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For changes touching data/*.py, validate user ID checks or explain why not needed

Files:

  • autogpt_platform/backend/backend/data/credit.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
🧠 Learnings (10)
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-05-07T18:48:14.242Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13040
File: autogpt_platform/backend/backend/blocks/llm.py:0-0
Timestamp: 2026-05-07T18:48:14.242Z
Learning: In this repository, isort may split imports from the same module into separate blocks when some imports are aliased (e.g., `from module import X as Y`) and others are not. Preserve the two-block layout when it results from isort (such as keeping `from openai.types.chat import ChatCompletion as OpenAIChatCompletion` separate from non-aliased imports from `openai.types.chat`). Do not treat that split as a style issue during review; merging them into a single block can fail CI with `Imports are incorrectly sorted and/or formatted`.

Applied to files:

  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/data/credit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-21T04:35:34.710Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12865
File: autogpt_platform/backend/backend/data/credit.py:1584-1584
Timestamp: 2026-04-21T04:35:34.710Z
Learning: When reviewing this codebase, don’t flag snake_case attribute names (e.g., `subscription_tier`, `stripe_customer_id`, `top_up_config`) on the app-layer Pydantic `User` model as “wrong” field names. These are correct for the app-layer model and are expected to be mapped from the Prisma-layer camelCase fields (e.g., `subscriptionTier`, `stripeCustomerId`) inside methods like `User.from_db()`. Only Prisma-returned/raw objects would use camelCase, but functions like `get_user_by_id(user_id: str)` are expected to return the Pydantic app-layer model.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-05-07T15:32:39.703Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13033
File: autogpt_platform/backend/backend/data/generate_data.py:111-117
Timestamp: 2026-05-07T15:32:39.703Z
Learning: When reviewing the Python data-generation layer, do not treat missing `user_id`/user filtering in calls to graph-metadata resolvers as a security issue if the `graph_id` inputs are already guaranteed to be user-scoped by earlier upstream SQL (e.g., `WHERE "userId" = ...`). In particular, `_resolve_agent_name(graph_id)` in `generate_data.py` correctly calls `get_graph_metadata(graph_id=graph_id)` without a `user_id` parameter by design, because name resolution must also work for user-executed shared/marketplace agents that the user may not own.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
🔇 Additional comments (5)
autogpt_platform/backend/backend/data/credit.py (2)

2253-2284: LGTM — cleanup helpers are well-structured.

The error-isolation layering is correct: per-session stripe.StripeError is swallowed inside the sync helper (so one unresponsive session doesn't block the others), and a list-level failure propagates out to the async wrapper where it's caught and logged before create_subscription_checkout proceeds.


2287-2324: LGTM — expiration is correctly placed before session creation.

autogpt_platform/backend/backend/api/features/v1.py (1)

1306-1318: LGTM — subscription tier sync is correctly guarded and resilient.

The guard chain (mode == "subscription"sub_id present → retrieve → sync) is correct, and using logger.exception (vs logger.warning) is appropriate since a failed tier sync in a billing webhook is a notable event that should surface in Sentry with a full traceback. The idempotency in sync_subscription_from_stripe safely handles the double-call case when customer.subscription.created arrives later.

autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py (2)

1-16: LGTM — test setup and TestClient configuration are correct.


31-68: LGTM — subscription sync path is correctly verified.

run_in_threadpool mock returning fake_sub is correctly scoped to this event path (only one run_in_threadpool call exists in the checkout.session.completed + subscription mode branch), and mock_sync.assert_called_once_with(fake_sub) is accurate since cast(dict, sub) is a no-op at runtime.

Comment thread autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py Outdated
@codecov

codecov Bot commented May 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.50%. Comparing base (f360ada) to head (aa899b6).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #13060      +/-   ##
==========================================
+ Coverage   70.43%   70.50%   +0.07%     
==========================================
  Files        2186     2191       +5     
  Lines      163824   164587     +763     
  Branches    16757    16814      +57     
==========================================
+ Hits       115389   116049     +660     
- Misses      45087    45177      +90     
- Partials     3348     3361      +13     
Flag Coverage Δ
platform-backend 79.56% <93.33%> (+0.08%) ⬆️
platform-frontend-e2e 31.04% <ø> (-0.58%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 79.56% <93.33%> (+0.08%) ⬆️
Platform Frontend 37.58% <ø> (-0.21%) ⬇️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@majdyz

majdyz commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

E2E Test Report — PR #13060

Branch: fix/stripe-checkout-sync-tier
Worktree: AutoGPT15
Date: 2026-05-08

Environment

  • Native backend (poetry run app) from AutoGPT15, confirmed on port 8006 via sys_path
  • Redis cluster (3-node, ports 17000-17002 via Docker)
  • Stripe keys not configured locally — unit tests used with mocks

Test Results

# Scenario Result
1 checkout.session.completed (subscription) → sync_subscription_from_stripe called ✅ PASS
2 checkout.session.completed (payment mode) → sync NOT called ✅ PASS
3 StripeError during subscription retrieval → webhook still returns 200 ✅ PASS
4 _expire_open_subscription_sessions with empty session list → no expire ✅ PASS
5 Mixed sessions → only subscription sessions expired (payment sessions skipped) ✅ PASS
6 Live /api/credits/invoices and /api/credits endpoints respond correctly ✅ PASS
7 Stripe webhook endpoint rejects unconfigured secret with 503 ✅ PASS

7/7 PASS

Code Review Notes

Bug fix 1 (NO_TIER paywall after successful checkout):

  • v1.py:1307-1318 — after checkout.session.completed with mode=subscription, the handler immediately fetches the subscription via stripe.Subscription.retrieve and calls sync_subscription_from_stripe. This eliminates the race where customer.subscription.created arrived late or was missed.
  • StripeErrors during retrieval are caught; webhook returns 200 (Stripe stops retrying).
  • The new sync path goes through all existing guards in sync_subscription_from_stripe (metadata.user_id cross-check, ENTERPRISE tier protection).

Bug fix 2 (phantom open invoices from abandoned checkout):

  • credit.py:2253-2299create_subscription_checkout calls _expire_open_subscription_sessions before creating a new session. Only subscription-mode sessions are expired; payment sessions untouched.
  • Expiry errors are warnings only — they don't block new checkout creation.

Result: ✅ PASS

@majdyz

majdyz commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

/pr-test results — local native stack (PR branch fix/stripe-checkout-sync-tier)

Environment

  • Backend: poetry run app from /Users/majdyz/Code/AutoGPT15 on port 8006 (PR branch code)
  • Frontend: pnpm run dev from /Users/majdyz/Code/AutoGPT15 on port 3001
  • Infra: local docker (supabase-db, supabase-auth, rabbitmq, redis-cluster)

Test 1: Unit tests — PASS ✅

All 5 new unit tests pass:

backend/api/features/v1_stripe_webhook_test.py::test_stripe_webhook_checkout_subscription_syncs_tier PASSED
backend/api/features/v1_stripe_webhook_test.py::test_stripe_webhook_checkout_payment_mode_does_not_sync_tier PASSED
backend/api/features/v1_stripe_webhook_test.py::test_stripe_webhook_checkout_subscription_stripe_error_does_not_break_webhook PASSED
backend/api/features/v1_stripe_webhook_test.py::test_expire_open_subscription_sessions_called_on_checkout PASSED
backend/api/features/v1_stripe_webhook_test.py::test_expire_open_subscription_sessions_expires_subscription_sessions PASSED

Test 2: Webhook endpoint reachability — PASS ✅

With STRIPE_WEBHOOK_SECRET configured, POST /api/credits/stripe_webhook correctly:

  • Returns 503 "Webhook not configured" → only when secret is unset (previous misconfigured backend instance)
  • Returns 400 "Invalid signature" → when secret is set and signature is wrong ✅
  • Route is live and reachable on the PR branch backend

Test 3: Paywall gate — NO_TIER user — PASS ✅

Created test user pr13060-test@autogpt.local with subscriptionTier = NO_TIER.

  • Navigating to / immediately redirects to /settings/billing — paywall active ✅
  • Billing page loads correctly (subscription section loads, payment method section renders) ✅

Test 4: Paywall bypass — PRO user — PASS ✅

After updating the same user to subscriptionTier = PRO via DB:

  • Navigating to / redirects to /copilot — no paywall blocking ✅
  • Copilot page renders fully with "Hey, pr13060-test" greeting ✅

Notes

  • STRIPE_API_KEY is empty locally so the subscription plan cards on billing page show loading skeletons (expected — no Stripe data)
  • sync_subscription_from_stripe call on checkout.session.completed is covered by unit tests; cannot fire live Stripe webhooks without real API key
  • _list_and_expire_open_subscription_sessions is covered by unit tests
  • "Failed to initialize onboarding" toast appears for the test user (no UserOnboarding row created) — pre-existing behavior, unrelated to this PR

Verdict: PASS ✅

Both fixes (tier sync on checkout + expire abandoned sessions) are logically correct per unit tests, webhook endpoint is live and signature-guarded, paywall correctly blocks NO_TIER and passes PRO users.

When a user is on NO_TIER, check Stripe for an active subscription and
sync their tier immediately — recovering from missed webhooks without
waiting for the next billing event. Gated behind a 5-minute Redis NX key
per user so the Stripe API is called at most once per 5 minutes, and only
for users who already have a Stripe customer record. The webhook fast-path
(checkout.session.completed) remains unchanged.
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/rate_limit.py (1)

914-948: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

get_user_tier reconciliation is also triggered when the DB is unreachable.

When _fetch_user_tier raises (line 930), tier is set to DEFAULT_TIER = NO_TIER. The guard if tier != SubscriptionTier.NO_TIER (line 939) doesn't distinguish "genuine NO_TIER from DB" from "DB unavailable → fallback". Both paths call _maybe_reconcile_stripe_tier, which then tries get_user_by_id (DB) again, fails, and wastes the NX slot. Downstream, the reconciliation gate is locked for 5 minutes on what was a DB transient, not a missed webhook.

This is a secondary consequence of the concern above; the delete-on-exception fix in _maybe_reconcile_stripe_tier mitigates it by releasing the gate when the reconciliation call itself errors out.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/copilot/rate_limit.py` around lines 914 -
948, get_user_tier currently treats a fallback DEFAULT_TIER (set when
_fetch_user_tier raises) the same as a genuine SubscriptionTier.NO_TIER, causing
unnecessary calls to _maybe_reconcile_stripe_tier after DB errors; change
get_user_tier to track whether the tier was returned from a successful
_fetch_user_tier (e.g., set a flag like fetched_ok = True/False when calling
_fetch_user_tier) and only call _maybe_reconcile_stripe_tier when fetched_ok is
True and the resolved tier equals SubscriptionTier.NO_TIER; keep the existing
exception logging and fallback behavior but avoid reconciliation when the
fallback was due to an exception.
🧹 Nitpick comments (1)
autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py (1)

36-73: ⚡ Quick win

Missing test: mode="subscription" with sub_id=None should not call sync_subscription_from_stripe.

The PR description states the handler only syncs "when a subscription ID is present", but there is no test asserting that sync_subscription_from_stripe is not called when mode="subscription" yet sub_id is None. This is a distinct conditional branch from the mode="payment" test and from the StripeError test.

✅ Suggested additional test
def test_stripe_webhook_checkout_subscription_no_sub_id_does_not_sync_tier(
    mocker: pytest_mock.MockFixture,
) -> None:
    mocker.patch(
        "stripe.Webhook.construct_event",
        return_value=_make_checkout_event("subscription", None),
    )
    mocker.patch(
        "backend.api.features.v1.settings.secrets.stripe_webhook_secret",
        new="whsec_test",
    )
    mocker.patch(
        "backend.api.features.v1.UserCredit.fulfill_checkout", new_callable=AsyncMock
    )
    mock_sync = mocker.patch(
        "backend.api.features.v1.sync_subscription_from_stripe", new_callable=AsyncMock
    )

    response = client.post(
        "/credits/stripe_webhook",
        content=b"{}",
        headers={"stripe-signature": "t=1,v1=sig"},
    )

    assert response.status_code == 200
    mock_sync.assert_not_called()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py`
around lines 36 - 73, Add a new test to cover the branch where checkout mode is
"subscription" but the returned subscription id is None; specifically create a
test named
test_stripe_webhook_checkout_subscription_no_sub_id_does_not_sync_tier that
mocks stripe.Webhook.construct_event to return
_make_checkout_event("subscription", None), patches
backend.api.features.v1.settings.secrets.stripe_webhook_secret, patches
UserCredit.fulfill_checkout as AsyncMock, and patches
sync_subscription_from_stripe as AsyncMock, then POST to
"/credits/stripe_webhook" and assert response.status_code == 200 and that
sync_subscription_from_stripe.assert_not_called() to ensure
sync_subscription_from_stripe is not invoked when sub_id is None.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/rate_limit.py`:
- Around line 887-911: The NX gate set in _maybe_reconcile_stripe_tier is never
released on exceptions, so when reconcile_stripe_tier_for_user(user_id) raises
the Redis key (f"{_STRIPE_RECONCILE_PREFIX}{user_id}") remains set for
_STRIPE_RECONCILE_TTL and blocks retries; fix by deleting that key when an
exception occurs: obtain the same redis instance from get_redis_async(), call
await redis.delete(f"{_STRIPE_RECONCILE_PREFIX}{user_id}") inside the except
Exception block (before logging/returning False) so the gate is released on
transient failures, leaving the existing behavior unchanged when reconciliation
succeeds or intentionally returns False.

---

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/rate_limit.py`:
- Around line 914-948: get_user_tier currently treats a fallback DEFAULT_TIER
(set when _fetch_user_tier raises) the same as a genuine
SubscriptionTier.NO_TIER, causing unnecessary calls to
_maybe_reconcile_stripe_tier after DB errors; change get_user_tier to track
whether the tier was returned from a successful _fetch_user_tier (e.g., set a
flag like fetched_ok = True/False when calling _fetch_user_tier) and only call
_maybe_reconcile_stripe_tier when fetched_ok is True and the resolved tier
equals SubscriptionTier.NO_TIER; keep the existing exception logging and
fallback behavior but avoid reconciliation when the fallback was due to an
exception.

---

Nitpick comments:
In `@autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py`:
- Around line 36-73: Add a new test to cover the branch where checkout mode is
"subscription" but the returned subscription id is None; specifically create a
test named
test_stripe_webhook_checkout_subscription_no_sub_id_does_not_sync_tier that
mocks stripe.Webhook.construct_event to return
_make_checkout_event("subscription", None), patches
backend.api.features.v1.settings.secrets.stripe_webhook_secret, patches
UserCredit.fulfill_checkout as AsyncMock, and patches
sync_subscription_from_stripe as AsyncMock, then POST to
"/credits/stripe_webhook" and assert response.status_code == 200 and that
sync_subscription_from_stripe.assert_not_called() to ensure
sync_subscription_from_stripe is not invoked when sub_id is None.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d67df08a-7db3-44f9-b854-40317b64d5d9

📥 Commits

Reviewing files that changed from the base of the PR and between f5ec25a and 1f523c5.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/data/credit.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • autogpt_platform/backend/backend/data/credit.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: type-check (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: type-check (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: type-check (3.12)
  • GitHub Check: end-to-end tests
  • GitHub Check: types
  • GitHub Check: Analyze (typescript)
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/backend/backend/api/features/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Update routes in '/backend/backend/api/features/' and add/update Pydantic models in the same directory for API development

Files:

  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/backend/**/api/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/api/**/*.py: Use Security() instead of Depends() for authentication dependencies to get proper OpenAPI security specification
Follow SSE (Server-Sent Events) protocol: use data: lines for frontend-parsed events (must match Zod schema) and : comment lines for heartbeats/status

Files:

  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
🧠 Learnings (10)
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
📚 Learning: 2026-04-01T04:17:41.600Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:41.600Z
Learning: When reviewing AutoGPT Copilot tool implementations, accept that `readOnlyHint=True` (provided via `ToolAnnotations`) may be applied unconditionally to *all* tools—even tools that have side effects (e.g., `bash_exec`, `write_workspace_file`, or other write/save operations). Do **not** flag these tools for having `readOnlyHint=True`; this is intentional to enable fully-parallel dispatch by the Anthropic SDK/CLI and has been E2E validated. Only flag `readOnlyHint` issues if they conflict with the established `ToolAnnotations` behavior (e.g., missing/incorrect propagation relative to the intended annotation mechanism).

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📚 Learning: 2026-05-07T18:48:14.242Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13040
File: autogpt_platform/backend/backend/blocks/llm.py:0-0
Timestamp: 2026-05-07T18:48:14.242Z
Learning: In this repository, isort may split imports from the same module into separate blocks when some imports are aliased (e.g., `from module import X as Y`) and others are not. Preserve the two-block layout when it results from isort (such as keeping `from openai.types.chat import ChatCompletion as OpenAIChatCompletion` separate from non-aliased imports from `openai.types.chat`). Do not treat that split as a style issue during review; merging them into a single block can fail CI with `Imports are incorrectly sorted and/or formatted`.

Applied to files:

  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
🔇 Additional comments (1)
autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py (1)

137-238: LGTM — session expiration and reconciliation tests are well-structured.

The _list_and_expire_open_subscription_sessions tests correctly verify filter semantics (empty list → no expiry; mixed-mode sessions → only subscription mode expired). The three reconcile_stripe_tier_for_user async tests cover all three branches (no customer, no active sub, active sub with sync). Mock targets all follow where-used patching (backend.data.credit.*).

Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/data/credit.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@autogpt_platform/backend/backend/data/credit.py`:
- Around line 2269-2273: The current call to stripe.checkout.Session.list in the
code uses limit=20 but lacks pagination, so only the first 20 sessions are
retrieved, potentially missing additional open sessions. To fix this, implement
pagination by using the 'starting_after' parameter with the last session ID from
each page, repeatedly fetching new pages until no more sessions are returned.
Modify the logic around the 'sessions' variable to repeatedly request pages of
open sessions for the given customer using this pagination approach.
- Around line 2296-2310: The reconcile_stripe_tier_for_user function can
propagate exceptions from _get_active_subscription; wrap the Stripe lookup and
subsequent call to sync_subscription_from_stripe in a try/except that catches
exceptions from Stripe/API calls (or a broad Exception if specific error types
are not available) and return False on any failure so the function always honors
its bool contract. Keep the early return when user.stripe_customer_id is
missing, but around the call to
_get_active_subscription(user.stripe_customer_id) and await
sync_subscription_from_stripe(cast(dict, sub)) add error handling (log the error
via your logger) and return False instead of letting the exception bubble.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 230501d6-7b30-4353-bb35-120108be64d2

📥 Commits

Reviewing files that changed from the base of the PR and between 1f523c5 and 17c48fe.

📒 Files selected for processing (4)
  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/data/credit.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/copilot/rate_limit.py
  • autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: check API types
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: end-to-end tests
  • GitHub Check: Analyze (python)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (4)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/data/credit.py
autogpt_platform/backend/backend/data/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All data access in backend requires user ID checks; verify this for any 'data/*.py' changes

Files:

  • autogpt_platform/backend/backend/data/credit.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/data/credit.py
autogpt_platform/**/data/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For changes touching data/*.py, validate user ID checks or explain why not needed

Files:

  • autogpt_platform/backend/backend/data/credit.py
🧠 Learnings (10)
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-04-21T04:35:34.710Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12865
File: autogpt_platform/backend/backend/data/credit.py:1584-1584
Timestamp: 2026-04-21T04:35:34.710Z
Learning: When reviewing this codebase, don’t flag snake_case attribute names (e.g., `subscription_tier`, `stripe_customer_id`, `top_up_config`) on the app-layer Pydantic `User` model as “wrong” field names. These are correct for the app-layer model and are expected to be mapped from the Prisma-layer camelCase fields (e.g., `subscriptionTier`, `stripeCustomerId`) inside methods like `User.from_db()`. Only Prisma-returned/raw objects would use camelCase, but functions like `get_user_by_id(user_id: str)` are expected to return the Pydantic app-layer model.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-05-07T15:32:39.703Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13033
File: autogpt_platform/backend/backend/data/generate_data.py:111-117
Timestamp: 2026-05-07T15:32:39.703Z
Learning: When reviewing the Python data-generation layer, do not treat missing `user_id`/user filtering in calls to graph-metadata resolvers as a security issue if the `graph_id` inputs are already guaranteed to be user-scoped by earlier upstream SQL (e.g., `WHERE "userId" = ...`). In particular, `_resolve_agent_name(graph_id)` in `generate_data.py` correctly calls `get_graph_metadata(graph_id=graph_id)` without a `user_id` parameter by design, because name resolution must also work for user-executed shared/marketplace agents that the user may not own.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
📚 Learning: 2026-05-07T18:48:14.242Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13040
File: autogpt_platform/backend/backend/blocks/llm.py:0-0
Timestamp: 2026-05-07T18:48:14.242Z
Learning: In this repository, isort may split imports from the same module into separate blocks when some imports are aliased (e.g., `from module import X as Y`) and others are not. Preserve the two-block layout when it results from isort (such as keeping `from openai.types.chat import ChatCompletion as OpenAIChatCompletion` separate from non-aliased imports from `openai.types.chat`). Do not treat that split as a style issue during review; merging them into a single block can fail CI with `Imports are incorrectly sorted and/or formatted`.

Applied to files:

  • autogpt_platform/backend/backend/data/credit.py
🔇 Additional comments (2)
autogpt_platform/backend/backend/data/credit.py (2)

2284-2293: Good non-blocking error boundary for session-expiry pre-cleanup.

Catching Stripe failures in _expire_open_subscription_sessions keeps checkout creation from being blocked by cleanup failures.


2324-2326: Good ordering in checkout creation path.

Running stale session expiry right after resolving customer_id and before creating a new subscription checkout session is the correct placement.

Comment thread autogpt_platform/backend/backend/data/credit.py Outdated
Comment thread autogpt_platform/backend/backend/data/credit.py
@majdyz majdyz changed the title fix(backend): sync subscription tier on checkout + expire abandoned sessions fix(backend): sync tier on checkout + expire abandoned sessions + lazy Stripe reconciliation May 8, 2026
…reconcile Stripe call

- _list_and_expire_open_subscription_sessions: paginate with limit=100 so
  customers with >20 open sessions are fully cleared; remove dead limit=20 cap
- _expire_open_subscription_sessions: catch Exception (not just StripeError) so
  unexpected errors from the threadpool don't block checkout creation
- reconcile_stripe_tier_for_user: wrap _get_active_subscription in try/except so
  Stripe API errors return False instead of propagating through get_user_tier
- tests: update limit assertion, add has_more=False, add pagination smoke test
Comment thread autogpt_platform/backend/backend/api/features/v1.py Outdated
Comment thread autogpt_platform/backend/backend/data/credit.py
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/api/features/v1.py Outdated
Comment thread autogpt_platform/backend/backend/api/features/v1.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Comment thread autogpt_platform/backend/backend/copilot/rate_limit.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end size/l

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant