Skip to content

[Billing Credits] PR-I4: Wallet-locked + concurrency error states#1995

Merged
Vu-John merged 1 commit intomainfrom
05-01-_billing_credits_pr-i4_wallet-locked_concurrency_error_states
May 2, 2026
Merged

[Billing Credits] PR-I4: Wallet-locked + concurrency error states#1995
Vu-John merged 1 commit intomainfrom
05-01-_billing_credits_pr-i4_wallet-locked_concurrency_error_states

Conversation

@Vu-John
Copy link
Copy Markdown
Collaborator

@Vu-John Vu-John commented May 2, 2026

[Billing Credits] PR-I4: Wallet-locked + concurrency error states

The server now returns two new error states the inspector currently renders
generically. This PR threads the new fields through the chat error parser
and renders distinct UX for each, plus tightens one top-up dialog edge case.

What's new

1. Wallet-locked banner — when the server marks an account as paused
(walletLocked: true), the chat error renders a dedicated "Account under
review" banner with a contact-support link. No top-up button, no retry —
the user can't self-serve out of this state.

┌─ Account under review ───────────────────────────────────┐
│ ⛨  We've paused this account while a recent payment is   │
│    reviewed. Reach out to support to get back in.        │
│                                          [Reset chat]    │
└──────────────────────────────────────────────────────────┘

2. Concurrency-throttle retry UI — when the server returns a
rate-limit with limitKind: "concurrency", the chat error renders a
transient banner with a small Retry button and a seconds-level
countdown. No top-up CTA (topping up doesn't help — the user just needs
to wait).

┌─ Another credit-funded chat is finishing. Retry in 8 seconds. [Retry]

3. Existing rate-limit + canTopUp path — unchanged. Daily quota
exhausted with a paid wallet option still shows the existing
"Top up to keep chatting" CTA.

Implementation

  • chat-helpers.ts: FormattedError gains three optional fields
    (walletLocked?: boolean, limitKind?: "total" | "concurrency",
    retryAfterMs?: number). Both parser branches (rate-limit and
    generic-structured) defensively extract them. retryAfterMs is
    emitted only for the concurrency case so the existing toEqual
    assertions on rate-limit results stay tight.
  • error.tsx: two new early-return branches at the top of the
    component for the locked and concurrency states. The existing
    rate-limit / generic / auth-error paths are unchanged.
  • ChatTabV2.tsx: a new handleRetryConcurrencyMessage callback
    reuses the existing lastSentUserMessageRef to resubmit the user's
    last typed message. The onRetry prop is gated on the error being a
    concurrency throttle so unrelated retryable errors don't surface a
    Retry button.
  • useCreditTopup.ts: the startCheckout catch block now detects
    the server's "Too many top-up attempts" message and re-throws a
    sanitized "You've hit the top-up rate limit. Try again in a few
    minutes." The dialog's existing toast picks up the new message, so
    no double-toast and no unhandled propagation.

Tests

Added 3 parser tests in chat-helpers-clone.test.ts:

  • Wallet-locked field passes through.
  • Concurrency limitKind + retryAfterMs pass through together.
  • Legacy 429 without either field — both undefined, no crash.

The one existing assertion that turned out to be sensitive to the new
limitKind: "total" field (the JSON included it but the assertion
didn't) was extended to expect it.

error.tsx has no existing render-test pattern, so per the spec the
new states get manual verification rather than fabricated tests.

Verification (manual)

  • Craft a 429 with walletLocked: true → "Account under review"
    banner, no top-up, mailto link present.
  • Craft a 429 with limitKind: "concurrency" and retryAfter: 8000
    transient banner with "Retry in 8 seconds" + Retry button. Click
    Retry → resubmits the last typed user message.
  • Existing daily-limit + canTopUp path → unchanged.
  • Simulate createCreditCheckoutSession rejecting with "Too many
    top-up attempts. Try again in 5 minutes." → toast surfaces the
    friendlier sanitized copy; dialog doesn't crash.

Local run of all touched suites green:

  • chat-helpers-clone.test.ts — 12 tests
  • client/src/components/billing — 23 tests across 5 files
  • client/src/hooks/__tests__/useCreditTopup* — 19 tests across 2 files
  • client/src/components/chat-v2/** and OrganizationsTab.billing.test.tsx — 444 tests across 38 files

Stack

This PR is a logical follow-up to PR-I3 (the existing billing-credits
stack), but submitted independently against main rather than stacked.
It builds on the FormattedError infrastructure introduced in PR-I1, so
expect to rebase once the I-stack merges.

Rebased 2026-05-01 onto the redesigned PR-I3 tip — the I3 design
simplification (drop dollar values from the bars, retire the activity
table, switch to hasPurchaseHistory boolean) does not interact with
this PR's scope. Diff cleanly reapplied with no conflicts.

Risk

Low. All new fields are optional; missing or wrong-type values default
to undefined (no crash, no false positives). The new UI states are
gated on explicit fields, so legacy responses keep their existing
rendering. The dialog's existing error path remains the same — only the
message it surfaces is now sanitized.

@chelojimenez
Copy link
Copy Markdown
Contributor

chelojimenez commented May 2, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link
Copy Markdown
Collaborator Author

Vu-John commented May 2, 2026

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Internal preview

Preview URL will appear in Railway after the deploy finishes.
Deployed commit: eb2294a
PR head commit: ec360b2
Backend target: staging fallback.
Access is employee-only in non-production environments.

@Vu-John Vu-John force-pushed the 05-01-_billing_credits_pr-i4_wallet-locked_concurrency_error_states branch from fddd1b2 to 7a489de Compare May 2, 2026 01:48
@Vu-John Vu-John force-pushed the 04-30-_billing_credits_pr-i3_credit_usage_card_activity_table branch from 277bf6d to 3dfc063 Compare May 2, 2026 01:48
@Vu-John Vu-John temporarily deployed to preview-pr-1995 May 2, 2026 01:48 — with GitHub Actions Inactive
@Vu-John Vu-John force-pushed the 05-01-_billing_credits_pr-i4_wallet-locked_concurrency_error_states branch from 7a489de to bcbc7de Compare May 2, 2026 03:46
@Vu-John Vu-John force-pushed the 04-30-_billing_credits_pr-i3_credit_usage_card_activity_table branch from 3dfc063 to 0c7f604 Compare May 2, 2026 03:46
@Vu-John Vu-John marked this pull request as ready for review May 2, 2026 03:47
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels May 2, 2026
@Vu-John Vu-John changed the base branch from 04-30-_billing_credits_pr-i3_credit_usage_card_activity_table to main May 2, 2026 03:57
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels May 2, 2026
@Vu-John Vu-John force-pushed the 05-01-_billing_credits_pr-i4_wallet-locked_concurrency_error_states branch from bcbc7de to ec360b2 Compare May 2, 2026 03:59
@dosubot dosubot Bot removed the size:XXL This PR changes 1000+ lines, ignoring generated files. label May 2, 2026
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label May 2, 2026
@Vu-John Vu-John merged commit 9acb40d into main May 2, 2026
7 of 8 checks passed
@Vu-John Vu-John deleted the 05-01-_billing_credits_pr-i4_wallet-locked_concurrency_error_states branch May 2, 2026 03:59
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 342710ad-47fb-41b5-870c-7b103d6d7230

📥 Commits

Reviewing files that changed from the base of the PR and between 67c30b5 and ec360b2.

📒 Files selected for processing (5)
  • mcpjam-inspector/client/src/components/ChatTabV2.tsx
  • mcpjam-inspector/client/src/components/chat-v2/error.tsx
  • mcpjam-inspector/client/src/components/chat-v2/shared/__tests__/chat-helpers-clone.test.ts
  • mcpjam-inspector/client/src/components/chat-v2/shared/chat-helpers.ts
  • mcpjam-inspector/client/src/hooks/useCreditTopup.ts

Walkthrough

This pull request extends error handling throughout the chat interface to distinguish between total rate limits and concurrency throttles, and to support wallet-locked account states. The ErrorBox component now renders dedicated UI for locked accounts and concurrency-throttled requests (with retry-after timing). The chat system tracks and retries concurrency-throttled messages. Error formatting pipelines were updated to parse and preserve walletLocked, limitKind, and retryAfterMs fields from server responses. The top-up hook now surfaces a user-friendly message when rate-limited during checkout. Test coverage was added for all three error state categories.


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants