[Billing Credits] PR-I4: Wallet-locked + concurrency error states#1992
Closed
[Billing Credits] PR-I4: Wallet-locked + concurrency error states#1992
Conversation
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Internal previewPreview URL: https://mcp-inspector-pr-1992.up.railway.app |
Threads two new error states from the server through the chat error parser and renders distinct UX for each, plus tightens a top-up dialog edge case. - FormattedError gains optional walletLocked, limitKind, and retryAfterMs fields. - Chat error banner now renders three states in priority order: walletLocked → "Account under review" with a contact-support link; limitKind="concurrency" → transient retry banner with seconds-level countdown and a Retry button; otherwise the existing rate-limit / generic rendering is unchanged. - ChatTabV2 surfaces the concurrency Retry handler only when the error is concurrency-classified, so unrelated retryable errors don't grow a Retry button. - useCreditTopup re-shapes the server's "Too many top-up attempts" error into a friendlier user message before re-throwing, so the dialog's existing toast picks it up cleanly. Test coverage extends the parser tests for the two new fields and the legacy back-compat case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
b6df079 to
b465315
Compare
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[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 underreview" banner with a contact-support link. No top-up button, no retry —
the user can't self-serve out of this state.
2. Concurrency-throttle retry UI — when the server returns a
rate-limit with
limitKind: "concurrency", the chat error renders atransient 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).
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:FormattedErrorgains three optional fields(
walletLocked?: boolean,limitKind?: "total" | "concurrency",retryAfterMs?: number). Both parser branches (rate-limit andgeneric-structured) defensively extract them.
retryAfterMsisemitted only for the concurrency case so the existing
toEqualassertions on rate-limit results stay tight.
error.tsx: two new early-return branches at the top of thecomponent for the locked and concurrency states. The existing
rate-limit / generic / auth-error paths are unchanged.
ChatTabV2.tsx: a newhandleRetryConcurrencyMessagecallbackreuses the existing
lastSentUserMessageRefto resubmit the user'slast typed message. The
onRetryprop is gated on the error being aconcurrency throttle so unrelated retryable errors don't surface a
Retry button.
useCreditTopup.ts: thestartCheckoutcatch block now detectsthe 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:limitKind+retryAfterMspass through together.The one existing assertion that turned out to be sensitive to the new
limitKind: "total"field (the JSON included it but the assertiondidn't) was extended to expect it.
error.tsxhas no existing render-test pattern, so per the spec thenew states get manual verification rather than fabricated tests.
Verification (manual)
walletLocked: true→ "Account under review"banner, no top-up, mailto link present.
limitKind: "concurrency"andretryAfter: 8000→transient banner with "Retry in 8 seconds" + Retry button. Click
Retry → resubmits the last typed user message.
createCreditCheckoutSessionrejecting with "Too manytop-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 testsclient/src/components/billing— 23 tests across 5 filesclient/src/hooks/__tests__/useCreditTopup*— 19 tests across 2 filesclient/src/components/chat-v2/**andOrganizationsTab.billing.test.tsx— 444 tests across 38 filesStack
This PR is a logical follow-up to PR-I3 (the existing billing-credits
stack), but submitted independently against
mainrather than stacked.It builds on the
FormattedErrorinfrastructure introduced in PR-I1, soexpect 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
hasPurchaseHistoryboolean) does not interact withthis 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 aregated 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.