fix(backend): sync tier on checkout + expire abandoned sessions + lazy Stripe reconciliation - #13060
Conversation
…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
|
This PR targets the Automatically setting the base branch to |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughExpires 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. ChangesSubscription Checkout Session Management
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🟢 Low Risk — File Overlap OnlyThese 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: |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
autogpt_platform/backend/backend/api/features/v1.pyautogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.pyautogpt_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: Usepoetry run ...command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies likeopenpyxl
Use absolute imports withfrom 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 — avoidhasattr/getattr/isinstancefor 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%sfor deferred interpolation indebuglog statements for efficiency; use f-strings elsewhere for readability (e.g.,logger.debug("Processing %s items", count)vslogger.info(f"Processing {count} items"))
Sanitize error paths by usingos.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
Usetransaction=Truefor Redis pipelines to ensure atomicity on multi-step operations
Usemax(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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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: UseSecurity()instead ofDepends()for authentication dependencies to get proper OpenAPI security specification
Follow SSE (Server-Sent Events) protocol: usedata:lines for frontend-parsed events (must match Zod schema) and: commentlines for heartbeats/status
Files:
autogpt_platform/backend/backend/api/features/v1.pyautogpt_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.pynaming 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
UseAsyncMockfromunittest.mockfor async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with@pytest.mark.xfailbefore implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, usepoetry run pytest path/to/test.py --snapshot-update; always review snapshot changes withgit diffbefore 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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.pyautogpt_platform/backend/backend/data/credit.pyautogpt_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.StripeErroris 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 beforecreate_subscription_checkoutproceeds.
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_idpresent → retrieve → sync) is correct, and usinglogger.exception(vslogger.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 insync_subscription_from_stripesafely handles the double-call case whencustomer.subscription.createdarrives later.autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.py (2)
1-16: LGTM — test setup andTestClientconfiguration are correct.
31-68: LGTM — subscription sync path is correctly verified.
run_in_threadpoolmock returningfake_subis correctly scoped to this event path (only onerun_in_threadpoolcall exists in thecheckout.session.completed+ subscription mode branch), andmock_sync.assert_called_once_with(fake_sub)is accurate sincecast(dict, sub)is a no-op at runtime.
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
E2E Test Report — PR #13060Branch: fix/stripe-checkout-sync-tier Environment
Test Results
7/7 PASS Code Review NotesBug fix 1 (NO_TIER paywall after successful checkout):
Bug fix 2 (phantom open invoices from abandoned checkout):
Result: ✅ PASS |
/pr-test results — local native stack (PR branch
|
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.
There was a problem hiding this comment.
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_tierreconciliation is also triggered when the DB is unreachable.When
_fetch_user_tierraises (line 930),tieris set toDEFAULT_TIER = NO_TIER. The guardif 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 triesget_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_tiermitigates 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 winMissing test:
mode="subscription"withsub_id=Noneshould not callsync_subscription_from_stripe.The PR description states the handler only syncs "when a
subscriptionID is present", but there is no test asserting thatsync_subscription_from_stripeis not called whenmode="subscription"yetsub_idisNone. This is a distinct conditional branch from themode="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
📒 Files selected for processing (3)
autogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.pyautogpt_platform/backend/backend/copilot/rate_limit.pyautogpt_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: Usepoetry run ...command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies likeopenpyxl
Use absolute imports withfrom 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 — avoidhasattr/getattr/isinstancefor 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%sfor deferred interpolation indebuglog statements for efficiency; use f-strings elsewhere for readability (e.g.,logger.debug("Processing %s items", count)vslogger.info(f"Processing {count} items"))
Sanitize error paths by usingos.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
Usetransaction=Truefor Redis pipelines to ensure atomicity on multi-step operations
Usemax(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.pyautogpt_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.pyautogpt_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: UseSecurity()instead ofDepends()for authentication dependencies to get proper OpenAPI security specification
Follow SSE (Server-Sent Events) protocol: usedata:lines for frontend-parsed events (must match Zod schema) and: commentlines 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.pynaming 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
UseAsyncMockfromunittest.mockfor async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with@pytest.mark.xfailbefore implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, usepoetry run pytest path/to/test.py --snapshot-update; always review snapshot changes withgit diffbefore 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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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_sessionstests correctly verify filter semantics (empty list → no expiry; mixed-mode sessions → only subscription mode expired). The threereconcile_stripe_tier_for_userasync tests cover all three branches (no customer, no active sub, active sub with sync). Mock targets all follow where-used patching (backend.data.credit.*).
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
autogpt_platform/backend/backend/api/features/v1.pyautogpt_platform/backend/backend/api/features/v1_stripe_webhook_test.pyautogpt_platform/backend/backend/copilot/rate_limit.pyautogpt_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: Usepoetry run ...command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies likeopenpyxl
Use absolute imports withfrom 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 — avoidhasattr/getattr/isinstancefor 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%sfor deferred interpolation indebuglog statements for efficiency; use f-strings elsewhere for readability (e.g.,logger.debug("Processing %s items", count)vslogger.info(f"Processing {count} items"))
Sanitize error paths by usingos.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
Usetransaction=Truefor Redis pipelines to ensure atomicity on multi-step operations
Usemax(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_sessionskeeps checkout creation from being blocked by cleanup failures.
2324-2326: Good ordering in checkout creation path.Running stale session expiry right after resolving
customer_idand before creating a new subscription checkout session is the correct placement.
…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
…pagating to callers
…concile confirmed NO_TIER)
…onciliation fires for NULL-tier users
Why / What / How
Why: Two related production billing bugs found via real-money testing, with a third systemic fix added:
NO_TIERand hit the paywall. Root cause: prod webhook was only subscribed tocheckout.session.completed—customer.subscription.createdwas never delivered, so the tier update never fired.NO_TIERindefinitely.What:
checkout.session.completednow immediately syncs the subscription tier via the newsync_tier_from_checkout_sessionhelper, making tier activation robust to missing/delayedcustomer.subscription.createdwebhooks (fast path)create_subscription_checkoutnow expires any open subscription sessions for the customer before creating a new one, clearing phantom open invoicesget_user_tierreturnsNO_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.pywebhook handler: oncheckout.session.completed, calls the newsync_tier_from_checkout_sessionhelper. Failures propagate as 5xx so Stripe retries the webhook (matching the existing pattern forcustomer.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 callssync_subscription_from_stripe; no-op for non-subscription modes or missing subscription ID._expire_open_subscription_sessions(customer_id)— async helper using nativelist_async/expire_async; paginates and expires open subscription sessions. Called at the start ofcreate_subscription_checkout.reconcile_stripe_tier_for_user(user_id)— looks up the active Stripe subscription for the customer and syncs the tier; returnsTrueon success.rate_limit.py:_maybe_reconcile_stripe_tiersets a Redis NX key (stripe_reconcile:{user_id}, 300s TTL) before callingreconcile_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 isNO_TIER(atier_from_dbflag distinguishes this from a DB read failure). After a successful reconciliation, a DB re-read failure is logged and falls back to staleNO_TIERrather than propagating to the caller.Changes 🏗️
backend/api/features/v1.py: Sync subscription tier oncheckout.session.completedvia newsync_tier_from_checkout_sessionhelper; failures propagate as 5xx so Stripe retriesbackend/data/credit.py: Addsync_tier_from_checkout_session,_expire_open_subscription_sessions,reconcile_stripe_tier_for_user; call expire helper at the start ofcreate_subscription_checkoutbackend/copilot/rate_limit.py: Add_maybe_reconcile_stripe_tierand lazy-reconcile call inget_user_tierwhen DB confirmsNO_TIER; skip reconciliation when initial DB read failedbackend/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, andreconcile_stripe_tier_for_user(no DB required)Checklist 📋
For code changes:
poetry run pytest backend/api/features/v1_stripe_webhook_test.py)