Skip to content

fix(shell-ui): surface OAuth callback errors from ?auth_error#1554

Open
kgarg2468 wants to merge 3 commits into
fix/305-network-vs-auth-errorsfrom
fix/301-auth-error-surfacing
Open

fix(shell-ui): surface OAuth callback errors from ?auth_error#1554
kgarg2468 wants to merge 3 commits into
fix/305-network-vs-auth-errorsfrom
fix/301-auth-error-surfacing

Conversation

@kgarg2468

@kgarg2468 kgarg2468 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Zitadel failures never reach the user: ?auth_error is set by the callback and read by nobody

packages/ai/src/ai/web/endpoints/auth_callback.py redirects OAuth failures back to the app as ?auth_error=<description> — but no web client ever reads the parameter (reported in rocketride-ai/rocketride-saas#301). Users who cancel at Zitadel, or hit any IdP-side failure, land back on the logged-out page with zero feedback, and the error param sticks around in the URL.

Fix: _bootstrap() now reads auth_error (plus standard error/error_description) right where it already parses ?code, strips it from the URL via the existing history.replaceState cleanup, and surfaces it through the #1553 recovery banner with a new errorKind: 'oauth-callback' | 'session' discriminator on ConnectionStatus: "Sign-in didn't complete: <IdP description>" with a Try again action that restarts the OAuth flow.

Stacked on #1553 (base is fix/305-network-vs-auth-errors; GitHub will retarget to develop when it merges — the diff here is one commit).

Testing

  • Tested locally — Playwright on the local Zitadel stack: /?auth_error=User%20cancelled%20the%20request → banner "Sign-in didn't complete: User cancelled the request", URL cleaned to /, logged-out landing intact, Try again navigates to the Zitadel login page. tsc --noEmit clean.
  • Commit messages follow conventional commits
  • No secrets or credentials included

Linked Issue

Fixes rocketride-ai/rocketride-saas#301

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved sign-in error messaging for OAuth callback failures.
    • Added a clear “Try again” action when sign-in doesn’t complete.
    • Preserved existing network retry and session-expired recovery messages.
    • Connection errors now retain their category across recovery attempts for more accurate guidance.

Read auth_error/error params in bootstrap, clean the URL, and show an
actionable banner (reuses ConnectionErrorBanner with errorKind).

Fixes rocketride-ai/rocketride-saas#301

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • develop
  • release/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f442e708-3c6b-463a-ab6f-a3ee24fa7b5f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ConnectionStatus now classifies OAuth callback and session failures. ConnectionManager detects and persists these categories, while ConnectionErrorBanner displays OAuth-specific recovery messaging and labels.

Changes

OAuth failure recovery

Layer / File(s) Summary
Status error-kind contract
packages/shared-ui/src/types/connection.ts
ConnectionStatus and lastFailure now expose optional 'oauth-callback' and 'session' error categories.
Connection failure classification
apps/shell-ui/src/connection/connection.ts
OAuth callback parameters produce an AUTH_FAILED status, stored-token failures receive the session category, normal transitions clear the category, and latched failures preserve it.
Recovery banner messaging
apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx
OAuth callback failures display a sign-in completion message and use the “Try again” label.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BrowserURL
  participant ConnectionManager
  participant ConnectionStatus
  participant ConnectionErrorBanner
  BrowserURL->>ConnectionManager: Provide OAuth callback error parameters
  ConnectionManager->>ConnectionStatus: Set AUTH_FAILED with errorKind oauth-callback
  ConnectionStatus->>ConnectionErrorBanner: Expose latched failure category
  ConnectionErrorBanner->>ConnectionManager: Trigger retry action
Loading

Possibly related PRs

Suggested reviewers: jmaionchi, stepmikhaylov, rod-christensen

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main shell-ui change: surfacing OAuth callback errors from the auth_error query parameter.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/301-auth-error-surfacing

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.

@github-actions github-actions Bot added the module:ui Chat UI and Dropper UI label Jul 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.

@kgarg2468

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx (1)

65-67: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add failure?.errorKind to the useEffect dependency array.

If lastFailure transitions between errorKind values ('session''oauth-callback') with the same state and lastError, the dismissed state won't reset because the deps don't capture errorKind changes. The banner would stay dismissed for the new failure type.

🔧 Proposed fix
 	useEffect(() => {
 		setDismissed(false);
-	}, [failure?.state, failure?.lastError]);
+	}, [failure?.state, failure?.lastError, failure?.errorKind]);
🤖 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 `@apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx` around lines
65 - 67, Update the useEffect in ConnectionErrorBanner to include
failure?.errorKind in its dependency array alongside failure?.state and
failure?.lastError, so dismissed resets when the failure type changes even if
the other values remain unchanged.
🤖 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 `@apps/shell-ui/src/connection/connection.ts`:
- Around line 457-458: Update the errorDescription assignment in the OAuth
error-handling flow to use logical OR chaining instead of nullish coalescing, so
empty auth_error or error_description values fall through to the next available
parameter and the existing if (errorDescription) banner path is preserved.

---

Outside diff comments:
In `@apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx`:
- Around line 65-67: Update the useEffect in ConnectionErrorBanner to include
failure?.errorKind in its dependency array alongside failure?.state and
failure?.lastError, so dismissed resets when the failure type changes even if
the other values remain unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 327198e5-8aec-4864-8dc0-7857195b279c

📥 Commits

Reviewing files that changed from the base of the PR and between 98e3c64 and df84618.

📒 Files selected for processing (3)
  • apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx
  • apps/shell-ui/src/connection/connection.ts
  • packages/shared-ui/src/types/connection.ts

Comment thread apps/shell-ui/src/connection/connection.ts Outdated
@kgarg2468

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Addressed in 3e4672a — error-param fallback uses logical OR so empty strings fall through, and failure?.errorKind joined the effect dependency array.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
git log --oneline -5 3e4672af 2>/dev/null || echo "commit not found locally"
git show 3e4672af --stat 2>/dev/null
git show 3e4672af -- apps/shell-ui/src/connection/connection.ts 2>/dev/null | head -100

Length of output: 2043


🏁 Script executed:

#!/bin/bash
git show 3e4672af -- apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx

Length of output: 1032


@kgarg2468 Confirmed — verified commit 3e4672a: the fallback chain now uses || instead of ?? so empty-string values from auth_error/error_description/error correctly fall through to the next param, and failure?.errorKind was added to the useEffect dependency array in ConnectionErrorBanner.tsx. Both look correct. Thanks for the fix!

@dylan-savage dylan-savage left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Over-broad param capture (connection.ts:456): matching bare error / error_description means any app URL landing on the redirect root with an unrelated ?error=… param (e.g. a deep link) is hijacked — the param is stripped and a false "Sign-in didn't complete" banner shown. Zitadel's own failures use auth_error; gate the generic OAuth params on a callback signal (presence of state / no code) rather than treating every bootstrap as a potential callback.

Addresses review: generic error/error_description could be hijacked by an
unrelated app deep-link. Our redirect_uri is the server callback, which wraps
every Zitadel failure as auth_error, so generic params never legitimately
reach the app root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kgarg2468

Copy link
Copy Markdown
Collaborator Author

Good catch @dylan-savage — fixed in 4547b78. Rather than gate the generic params on a callback signal, I dropped error/error_description entirely and now honor only auth_error. Our registered redirect_uri is the server callback, which wraps every Zitadel failure as auth_error before redirecting to the app — so the generic OAuth params never legitimately reach the app root and reading them only created the hijack surface you described.

Verified locally: /?auth_error=… still shows the banner and cleans the URL; /?error=…&error_description=… (unrelated deep-link) now shows no banner and leaves the URL untouched.

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

Labels

module:ui Chat UI and Dropper UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants