Skip to content

fix(ai): contain provider setup errors#2411

Open
aqilaziz wants to merge 1 commit into
tinyhumansai:mainfrom
aqilaziz:codex/2363-ai-provider-error-wrap
Open

fix(ai): contain provider setup errors#2411
aqilaziz wants to merge 1 commit into
tinyhumansai:mainfrom
aqilaziz:codex/2363-ai-provider-error-wrap

Conversation

@aqilaziz
Copy link
Copy Markdown
Contributor

@aqilaziz aqilaziz commented May 21, 2026

Summary

  • Contain long AI provider setup errors inside the settings modal
  • Show a short user-facing summary and keep raw provider payloads behind expandable details
  • Add focused coverage for long provider setup failures

Problem

Provider setup failures can include long URLs or JSON-like API payloads that overflow the LLM settings modal.

Solution

  • Normalize setup failures into a short summary plus optional raw detail
  • Redact endpoint URLs from the summary and cap overly long text
  • Render raw details in a wrapped, scrollable disclosure block

Submission Checklist

Required

  • I have formatted the code with pnpm format or an equivalent focused formatter check
  • I have linted the code with pnpm lint or verified the touched code path with focused checks
  • I have run the relevant tests, and they pass
  • I have included or updated tests for the change, or explained why not needed

Coverage

  • Added/updated tests for changed behavior
  • N/A: no coverage matrix row added or removed

Release / Manual Validation

  • Verified provider setup error handling with a focused unit test
  • N/A: settings modal UI-only change, no release checklist update needed

Linear Metadata

  • N/A: this PR is linked to a GitHub issue

Validation

  • prettier --check src/components/settings/panels/AIPanel.tsx src/components/settings/panels/__tests__/AIPanel.test.tsx
  • pnpm test:unit src/components/settings/panels/__tests__/AIPanel.test.tsx
  • git diff --check

Related

Closes #2363

AI Authored PR Metadata

  • Generated with AI assistance and manually validated with focused tests.

Summary by CodeRabbit

  • New Features

    • Structured error handling for AI provider setup with concise summaries and optional expandable details.
  • Improvements

    • Error UI now shows user-friendly messages and hides technical payloads until “Details” is expanded; submission failures are formatted consistently.
  • Tests

    • Added async test verifying long provider error payloads are suppressed in the summary and revealed only in expanded details.

Review Change Stack

@aqilaziz aqilaziz requested a review from a team May 21, 2026 04:57
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d59d8153-949f-4dc6-86a3-a7c63df521cf

📥 Commits

Reviewing files that changed from the base of the PR and between 03194b2 and 266a570.

📒 Files selected for processing (2)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx

📝 Walkthrough

Walkthrough

Adds a ProviderSetupError type and formatProviderSetupError() helper, introduces ProviderSetupErrorMessage to show a clipped summary with optional expandable details, updates ProviderKeyDialog and CloudProviderEditor to use the structured error shape, and adds a test verifying verbose OpenAI 401 errors are summarized and details are behind a disclosure.

Changes

Provider Setup Error Handling and Display

Layer / File(s) Summary
Error type and formatting foundation
app/src/components/settings/panels/AIPanel.tsx
Introduces ProviderSetupError with summary and optional detail, plus formatProviderSetupError() to normalize and truncate unknown thrown values for UI display.
ProviderKeyDialog integration
app/src/components/settings/panels/AIPanel.tsx
Changes local error state to `ProviderSetupError
ProviderSetupErrorMessage display component
app/src/components/settings/panels/AIPanel.tsx
New component renders error.summary and conditionally shows an expandable "Details" disclosure with error.detail in a preformatted block to avoid layout overflow.
CloudProviderEditor integration
app/src/components/settings/panels/AIPanel.tsx
Updates submitError to `ProviderSetupError
Test coverage for error summarization
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
Adds listProviderModels import, makes setCloudProviderKey/clearCloudProviderKey async mocks, and adds a UI test asserting a verbose 401 yields a generic "Could not reach OpenAI." summary while raw API-key text appears only in expanded details (tail payload omitted).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

working

Poem

🐰 A tiny hop to tame a giant error,
Summary up front, details kept nearer—
The modal stays neat, no long lines stray,
Click "Details" to see the full fray,
Debug safe and snug, hip-hop hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(ai): contain provider setup errors' directly and concisely summarizes the main change—improved error handling to contain provider setup errors in the LLM settings modal.
Linked Issues check ✅ Passed The PR successfully addresses all coding objectives from #2363: wraps error text, shows user-friendly summaries with details behind expandable sections, and adds unit test coverage for long errors.
Out of Scope Changes check ✅ Passed All changes in AIPanel.tsx and its test file are directly focused on containing provider setup errors with proper formatting and error component handling.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
app/src/components/settings/panels/AIPanel.tsx (2)

205-225: ⚡ Quick win

Internationalize the fallback error message.

The fallback message on line 207 is hardcoded in English. For consistency with the rest of the codebase (which uses the t() function for i18n), this should be internationalized.

Suggested change
 function formatProviderSetupError(err: unknown): ProviderSetupError {
   const raw = err instanceof Error ? err.message : String(err ?? '');
-  const fallback = 'Provider setup failed. Check the provider settings and try again.';
+  const fallback = t('settings.ai.providerSetupFallbackError');
   if (!raw.trim()) return { summary: fallback };

You'll need to add the translation key to your i18n resources and accept t as a parameter or use the useT() hook if this function is called within a component context. Alternatively, keep the function pure and handle the fallback message at the call sites.

🤖 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 `@app/src/components/settings/panels/AIPanel.tsx` around lines 205 - 225,
formatProviderSetupError currently uses a hardcoded English fallback string;
update it to use i18n by either (A) adding a translation key (e.g.
"provider.setup_failed") to your i18n resources and changing
formatProviderSetupError to accept a t function parameter (e.g.
formatProviderSetupError(err: unknown, t: TFunction)) and call
t('provider.setup_failed') instead of the literal, or (B) keep
formatProviderSetupError pure and remove the fallback here, returning
undefined/detail only and let callers (components using useT()) supply the
localized fallback; ensure ProviderSetupError consumers are updated accordingly
to use t() or useT() and add the new translation key to the locale files.

673-691: ⚡ Quick win

Internationalize the "Details" label.

The "Details" label on line 682 is hardcoded in English. This should use the t() function for internationalization, consistent with other user-facing strings in the component (e.g., line 619 uses t('settings.ai.connectProvider')).

Suggested change
-function ProviderSetupErrorMessage({ error }: { error: ProviderSetupError }) {
+function ProviderSetupErrorMessage({ error }: { error: ProviderSetupError }) {
+  const { t } = useT();
   return (
     <div className="min-w-0 rounded-md border border-red-200 dark:border-red-500/30 bg-red-50 dark:bg-red-500/10 px-3 py-2 text-xs text-red-700 dark:text-red-300">
       <p role="alert" className="font-medium leading-relaxed break-words [overflow-wrap:anywhere]">
         {error.summary}
       </p>
       {error.detail && (
         <details className="mt-2">
-          <summary className="cursor-pointer select-none text-[11px] font-medium text-red-700 dark:text-red-200">
-            Details
-          </summary>
+          <summary className="cursor-pointer select-none text-[11px] font-medium text-red-700 dark:text-red-200">
+            {t('settings.ai.errorDetails')}
+          </summary>
🤖 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 `@app/src/components/settings/panels/AIPanel.tsx` around lines 673 - 691,
ProviderSetupErrorMessage currently hardcodes the "Details" summary text;
replace that literal with a call to the i18n function (t) to match the rest of
the component. In the ProviderSetupErrorMessage component, change the <summary>
content from "Details" to t('<appropriate.key>') (e.g., t('settings.ai.details')
or whatever existing localization key you prefer), import/use the same t
function/context as the parent component so the label is internationalized
consistently.
app/src/components/settings/panels/__tests__/AIPanel.test.tsx (1)

344-366: ⚡ Quick win

Consider explicitly testing the expand/collapse behavior.

The test verifies that the friendly message appears in the alert and the raw error text exists in the dialog, which covers the core functionality. However, line 365 searches the entire dialog for the raw error text without explicitly verifying it's hidden before the user expands the "Details" section.

To more thoroughly test the "raw detail behind details" behavior described in the test name, consider:

  1. Asserting the raw text is not visible before expanding (e.g., checking the <details> element is closed or using a visibility query)
  2. Clicking the "Details" button/control to expand it
  3. Then asserting the raw error text is visible

This would more accurately verify the hide/show interaction rather than just checking that the content exists in the DOM.

✨ Example enhancement
  const alert = await within(dialog).findByRole('alert');
  expect(alert).toHaveTextContent(/Could not reach OpenAI\./i);
  expect(alert).not.toHaveTextContent('invalid_api_key');
- expect(within(dialog).getByText('Details')).toBeInTheDocument();
- expect(within(dialog).getByText(/invalid_api_key_invalid_api_key/)).toBeInTheDocument();
+ 
+ // Verify raw error is not visible initially (hidden behind details)
+ expect(screen.queryByText(/invalid_api_key_invalid_api_key/)).not.toBeVisible();
+ 
+ // Expand the details section
+ const detailsToggle = within(dialog).getByText('Details');
+ fireEvent.click(detailsToggle);
+ 
+ // Now raw error should be visible
+ await waitFor(() =>
+   expect(within(dialog).getByText(/invalid_api_key_invalid_api_key/)).toBeVisible()
+ );

Note: Adjust based on how ProviderSetupErrorMessage implements the details disclosure (e.g., <details>/<summary> vs. custom toggle).

🤖 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 `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx` around lines
344 - 366, Update the test in AIPanel.test.tsx to explicitly verify the
expand/collapse behavior: after confirming the friendly alert text, assert the
raw error text is not visible (e.g., query the <details> disclosure or use a
visibility assertion on the raw string
within(dialog).queryByText(/invalid_api_key_invalid_api_key/)). Then simulate
the user expanding the Details control (click the "Details" summary/button found
via within(dialog).getByText('Details') or getByRole) and finally assert the raw
error text is visible; reference the AIPanel render and the
ProviderSetupErrorMessage/Details control used in the dialog when updating the
assertions.
🤖 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.

Nitpick comments:
In `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx`:
- Around line 344-366: Update the test in AIPanel.test.tsx to explicitly verify
the expand/collapse behavior: after confirming the friendly alert text, assert
the raw error text is not visible (e.g., query the <details> disclosure or use a
visibility assertion on the raw string
within(dialog).queryByText(/invalid_api_key_invalid_api_key/)). Then simulate
the user expanding the Details control (click the "Details" summary/button found
via within(dialog).getByText('Details') or getByRole) and finally assert the raw
error text is visible; reference the AIPanel render and the
ProviderSetupErrorMessage/Details control used in the dialog when updating the
assertions.

In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 205-225: formatProviderSetupError currently uses a hardcoded
English fallback string; update it to use i18n by either (A) adding a
translation key (e.g. "provider.setup_failed") to your i18n resources and
changing formatProviderSetupError to accept a t function parameter (e.g.
formatProviderSetupError(err: unknown, t: TFunction)) and call
t('provider.setup_failed') instead of the literal, or (B) keep
formatProviderSetupError pure and remove the fallback here, returning
undefined/detail only and let callers (components using useT()) supply the
localized fallback; ensure ProviderSetupError consumers are updated accordingly
to use t() or useT() and add the new translation key to the locale files.
- Around line 673-691: ProviderSetupErrorMessage currently hardcodes the
"Details" summary text; replace that literal with a call to the i18n function
(t) to match the rest of the component. In the ProviderSetupErrorMessage
component, change the <summary> content from "Details" to t('<appropriate.key>')
(e.g., t('settings.ai.details') or whatever existing localization key you
prefer), import/use the same t function/context as the parent component so the
label is internationalized consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5a30f5c-cc4f-4577-926b-52ccced84f1d

📥 Commits

Reviewing files that changed from the base of the PR and between 6281aea and 2c3bbd2.

📒 Files selected for processing (2)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 21, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/components/settings/panels/__tests__/AIPanel.test.tsx (1)

344-366: ⚡ Quick win

Strengthen this test to verify disclosure behavior, not just DOM presence.

This currently checks detail text exists without expanding “Details”, so it doesn’t prove the raw payload is hidden until user action.

✅ Suggested assertion shape
   const alert = await within(dialog).findByRole('alert');
   expect(alert).toHaveTextContent(/Could not reach OpenAI\./i);
   expect(alert).not.toHaveTextContent('invalid_api_key');
-  expect(within(dialog).getByText('Details')).toBeInTheDocument();
-  expect(within(dialog).getByText(/invalid_api_key_invalid_api_key/)).toBeInTheDocument();
+  const detailsSummary = within(dialog).getByText('Details');
+  const details = detailsSummary.closest('details') as HTMLDetailsElement;
+  expect(details.open).toBe(false);
+  fireEvent.click(detailsSummary);
+  expect(details.open).toBe(true);
+  expect(within(dialog).getByText(/invalid_api_key_invalid_api_key/)).toBeInTheDocument();
🤖 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 `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx` around lines
344 - 366, The test currently only checks that the details text exists in the
DOM but doesn't verify that it's initially hidden and only revealed after user
action; update the 'keeps long provider setup errors contained with raw detail
behind details' test to assert that the raw payload string (e.g.,
'invalid_api_key_invalid_api_key') is not visible inside the alert element
initially (use within(dialog).findByRole('alert') and
expect(...).not.toHaveTextContent(...)), then simulate expanding the Details
control (fireEvent.click(on the element returned by
within(dialog).getByText('Details') or getByRole if available) and assert
afterwards that the raw payload is visible
(expect(within(dialog).getByText(/invalid_api_key_invalid_api_key/)).toBeInTheDocument()).
Ensure you still keep the existing checks for the generic error message and that
Details exists.
🤖 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 `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 223-224: The current code sets detail to the full raw provider
payload which can bloat React state; modify the assignment around const detail =
raw.trim() !== summary ? raw.trim() : undefined to enforce a maximum length
(e.g., const MAX_DETAIL_LENGTH = 1000) and if raw.trim() exceeds that limit,
truncate it (append an ellipsis) before setting detail, while preserving the
existing equality check with summary so detail remains undefined when identical
to summary; update any references that expect full raw to use the truncated
value or expose the full payload only on demand (e.g., via "view full error"
action).

---

Nitpick comments:
In `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx`:
- Around line 344-366: The test currently only checks that the details text
exists in the DOM but doesn't verify that it's initially hidden and only
revealed after user action; update the 'keeps long provider setup errors
contained with raw detail behind details' test to assert that the raw payload
string (e.g., 'invalid_api_key_invalid_api_key') is not visible inside the alert
element initially (use within(dialog).findByRole('alert') and
expect(...).not.toHaveTextContent(...)), then simulate expanding the Details
control (fireEvent.click(on the element returned by
within(dialog).getByText('Details') or getByRole if available) and assert
afterwards that the raw payload is visible
(expect(within(dialog).getByText(/invalid_api_key_invalid_api_key/)).toBeInTheDocument()).
Ensure you still keep the existing checks for the generic error message and that
Details exists.
🪄 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: 36ad4671-621f-499f-9353-44fd38a2ace1

📥 Commits

Reviewing files that changed from the base of the PR and between 2c3bbd2 and 03194b2.

📒 Files selected for processing (2)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx

Comment thread app/src/components/settings/panels/AIPanel.tsx Outdated
@aqilaziz aqilaziz force-pushed the codex/2363-ai-provider-error-wrap branch from 03194b2 to 266a570 Compare May 21, 2026 05:32
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provider setup error text overflows the modal

1 participant