Skip to content

fix(shell-ui): distinguish network from auth failures and surface both in a recovery banner#1553

Open
kgarg2468 wants to merge 3 commits into
developfrom
fix/305-network-vs-auth-errors
Open

fix(shell-ui): distinguish network from auth failures and surface both in a recovery banner#1553
kgarg2468 wants to merge 3 commits into
developfrom
fix/305-network-vs-auth-errors

Conversation

@kgarg2468

@kgarg2468 kgarg2468 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Network failures render as auth failures; stored-token reconnects fail silently; a dead server means an infinite splash

Three connected defects in the shell-ui connection layer (reported in rocketride-ai/rocketride-saas#305):

  • finishConnect classified AUTH vs other by string-matching the error message ('Authentication failed' / 'unknown user' / 'invalid credentials'), so transport timeouts could misreport as auth failures — and the SDK's typed AuthenticationException ("Invalid or revoked API key") matched neither pattern and surfaced as a network problem.
  • Stored-token reconnect failures were swallowed (catch { clearToken(); return null; }): the user lands on the logged-out page with no explanation, and a transient network blip destroys a valid token.
  • With the server fully unreachable, bootstrap awaited the transport attach forever → indefinite splash screen, nothing rendered.

Fix:

  • New typed ConnectionFailure (kind: 'auth' | 'network' | 'server') thrown at the sites that know the cause (remote-manager timeout → network, missing userId → auth); SDK AuthenticationException maps to auth. The string match survives only as a documented legacy fallback for untyped errors.
  • New ConnectionState.NETWORK_ERROR; network failures preserve the stored token (retryable), auth failures clear it.
  • Failures latch on ConnectionStatus.lastFailure: the SDK's persist-mode retry loop reports CONNECTING (up to 120 attempts) and a post-failure anonymous connect reports CONNECTED — either erased a purely state-driven failure before any UI could render it. Network latches auto-clear on reconnection; session-expiry persists until acted on.
  • Transport attach is bounded (10s race) so an unreachable server surfaces as a retryable network failure instead of an eternal splash.
  • New dismissible ConnectionErrorBanner (first consumer of the previously consumer-less useConnectionStatus() hook), mounted in ShellLayout: "Can't reach the server — check your connection and retry." + Retry, vs "Your session has expired — please sign in again." + Sign in.

Testing

  • Tested locally — full E2E on a local stack (Zitadel v4.15.1 + Login V2 + engine --saas), Playwright-driven:
    • engine stopped → network banner within ~10s, token preserved, no splash-hang; engine restarted → session auto-recovers with the same token
    • invalid rr_ token → "session has expired" banner + token cleared + Sign in → Zitadel
    • tsc --noEmit clean for shell-ui and shared-ui
  • Commit messages follow conventional commits
  • No secrets or credentials included

Linked Issue

Fixes rocketride-ai/rocketride-saas#305

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a dismissible, fixed connection error banner with tailored messaging and actions (Retry for network issues, Sign in for auth issues).
    • Introduced failure “latching” so the most recent unrecovered error is preserved in the UI until resolved.
  • Bug Fixes
    • Connection bootstrap/reconnect flows now time out instead of hanging indefinitely.
    • Improved failure handling with consistent typed error classification and clearer recovery behavior.
  • Chores / Improvements
    • Auth token persistence moved from session storage to local storage, including cross-tab updates and token-migration behavior.

Typed ConnectionFailure errors at throw sites, NETWORK_ERROR connection
state, no more silent stored-token reconnect failures, and a dismissible
recovery banner with retry / re-auth actions.

Fixes rocketride-ai/rocketride-saas#305

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6150c39c-097d-4017-8021-145862646b78

📥 Commits

Reviewing files that changed from the base of the PR and between 2945b57 and 6e314b3.

📒 Files selected for processing (5)
  • apps/shell-ui/src/auth/ApiKeyAuthProvider.ts
  • apps/shell-ui/src/auth/CloudAuthProvider.ts
  • apps/shell-ui/src/connection/connection.ts
  • apps/shell-ui/src/connection/tokenStorageUpdate.ts
  • apps/shell-ui/src/constants.ts

📝 Walkthrough

Walkthrough

Connection recovery now classifies failures with typed errors, applies bounded attach and token-login timeouts, latches unresolved failures in connection status, persists tokens in localStorage, and displays retry or sign-in actions through a new shell banner.

Changes

Connection recovery and error reporting

Layer / File(s) Summary
Typed failure contracts and remote error mapping
packages/shared-ui/src/types/connection.ts, apps/shell-ui/src/connection/errors.ts, apps/shell-ui/src/connection/remote-manager.ts
Defines typed failures, timeout handling, latched failure status, and typed remote login error mapping.
Persistent token storage and cross-tab updates
apps/shell-ui/src/auth/*, apps/shell-ui/src/connection/tokenStorageUpdate.ts, apps/shell-ui/src/connection/constants.ts
Moves authentication tokens to localStorage, migrates legacy values, and responds to token changes across tabs.
Bootstrap, reconnect, and failure latching
apps/shell-ui/src/connection/connection.ts
Adds bounded attach and stored-token login flows, typed failure classification, selective token clearing, and failure latching across connection transitions.
Shell connection error banner
apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx, apps/shell-ui/src/components/layout/ShellLayout.tsx
Adds a dismissible alert with retry and sign-in actions and renders it in the shell layout.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ShellLayout
  participant ConnectionErrorBanner
  participant ConnectionManager
  participant RemoteManager
  participant ConnectionStatus
  ShellLayout->>ConnectionErrorBanner: render latched failure
  ConnectionErrorBanner->>ConnectionManager: retry or start OAuth
  ConnectionManager->>RemoteManager: attach or login with stored token
  RemoteManager-->>ConnectionManager: success or typed failure
  ConnectionManager->>ConnectionStatus: update or latch failure
  ConnectionStatus-->>ShellLayout: updated status
Loading

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 change: distinguishing failure types and showing them in a recovery banner.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/305-network-vs-auth-errors
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/305-network-vs-auth-errors

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.

apps/shell-ui/src/auth/ApiKeyAuthProvider.ts

Oops! Something went wrong! :(

ESLint: 9.39.5

TypeError: expand is not a function
at Minimatch.braceExpand (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:271:10)
at Minimatch.make (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:180:33)
at new Minimatch (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:156:8)
at doMatch (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:422:13)
at match (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:756:11)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:772:10
at Array.some ()
at pathMatches (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:767:44)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:1368:8
at FlatConfigArray.forEach ()

apps/shell-ui/src/auth/CloudAuthProvider.ts

Oops! Something went wrong! :(

ESLint: 9.39.5

TypeError: expand is not a function
at Minimatch.braceExpand (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:271:10)
at Minimatch.make (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:180:33)
at new Minimatch (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:156:8)
at doMatch (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:422:13)
at match (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:756:11)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:772:10
at Array.some ()
at pathMatches (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:767:44)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:1368:8
at FlatConfigArray.forEach ()

apps/shell-ui/src/connection/connection.ts

Oops! Something went wrong! :(

ESLint: 9.39.5

TypeError: expand is not a function
at Minimatch.braceExpand (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:271:10)
at Minimatch.make (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:180:33)
at new Minimatch (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:156:8)
at doMatch (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:422:13)
at match (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:756:11)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:772:10
at Array.some ()
at pathMatches (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:767:44)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:1368:8
at FlatConfigArray.forEach ()

  • 2 others

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.

@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: 7

🤖 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/components/layout/ConnectionErrorBanner.tsx`:
- Line 83: Update the retry/sign-in button handler in ConnectionErrorBanner so
errors from onAction() are not silently discarded: catch unexpected exceptions
and log them with the component’s established logging mechanism, while
preserving the existing latched-status behavior for expected connection
failures.
- Around line 76-78: The default Retry handler in ConnectionErrorBanner’s
onAction must recover attach-timeout NETWORK_ERRORs when no token is stored.
Update the fallback around ConnectionManager.getInstance().reconnect() to invoke
bootstrap/attach again (or reload) when reconnect cannot proceed without a
token, while preserving the existing onRetry override and normal reconnect
behavior.

In `@apps/shell-ui/src/connection/connection.ts`:
- Around line 438-443: Extract the duplicated timeout race logic in the
connection class into a shared withTimeout helper that clears its timer in
finally after either promise settles. Replace both the 10-second _attachPromise
race and the corresponding 8-second timeout call site with this helper,
preserving their existing durations and ConnectionFailure messages.
- Around line 719-755: The comment does not request a code change: retain the
existing server-error classification in getConnectionFailureState, even though
current call sites only produce auth and network failures, so future server
failures map to ConnectionState.FAILED.
- Around line 433-452: Update the attach handling in _bootstrap so failures from
_attachPromise are not swallowed before the Promise.race, ensuring both
immediate attach errors and the timeout enter handleStoredTokenFailure as
network failures. Preserve the existing ConnectionFailure conversion and
return-null recovery flow.
- Around line 556-558: Replace the direct lastFailure assignment in the
authenticated connection flow with an updateConnectionStatus call that clears
lastFailure and emits shell:statusChange. Preserve the existing behavior of
resolving the latched authentication failure while ensuring useConnectionStatus
and ConnectionErrorBanner receive the update immediately.

In `@apps/shell-ui/src/connection/remote-manager.ts`:
- Around line 66-91: Extract a shared withTimeout utility for the repeated
Promise.race timeout pattern, accepting the operation promise, timeout duration,
and timeout-error factory. Update the login flow around client.login in the
connection manager and the corresponding flow in connection.ts to use this
helper, centralizing the timeout constant and ensuring timers are cleared when
the operation settles.
🪄 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: 3961805d-ebc0-46d8-8615-191409583771

📥 Commits

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

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

Comment thread apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx
Comment thread apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx Outdated
Comment thread apps/shell-ui/src/connection/connection.ts
Comment thread apps/shell-ui/src/connection/connection.ts Outdated
Comment thread apps/shell-ui/src/connection/connection.ts
Comment thread apps/shell-ui/src/connection/connection.ts
Comment thread apps/shell-ui/src/connection/remote-manager.ts
@kgarg2468

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Addressed in 2945b57 — raw attach rejections now propagate to bootstrap (banner in ~0.5s instead of waiting out the 10s race), authenticated recovery clears the latch via updateConnectionStatus (statusChange emitted), shared timer-clearing withTimeout helper replaces the duplicated races, Retry falls back to a reload when no transport ever attached, and recovery-action errors are logged. Re-verified E2E locally: server-down → banner 0.5s + token preserved; Retry → reload; server-restored → session recovers, banner clears.

@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 -1 2945b57a 2>/dev/null || echo "commit not found in shallow clone"
git show --stat 2945b57a 2>/dev/null | head -50

Length of output: 877


🏁 Script executed:

#!/bin/bash
git show 2945b57a -- apps/shell-ui/src/connection/connection.ts apps/shell-ui/src/connection/errors.ts apps/shell-ui/src/connection/remote-manager.ts apps/shell-ui/src/components/layout/ConnectionErrorBanner.tsx

Length of output: 8215


🏁 Script executed:

#!/bin/bash
grep -n "_attachPromise" apps/shell-ui/src/connection/connection.ts

Length of output: 398


@kgarg2468 Verified the fix in commit 2945b57a. All four points check out against the diff:

  • client.attach() is no longer immediately wrapped in a swallowing .catch; the rejection now propagates through this._attachPromise itself, so the withTimeout await at bootstrap surfaces the failure as soon as attach rejects (rather than waiting out the full 10s race). The .then().catch() chain still logs the error without introducing an unhandled-rejection risk, since a reaction is registered on the original promise either way.
  • withTimeout in errors.ts cleanly clears the timer in a finally, and is now shared across bootstrap-attach, stored-token connect, and RemoteManager.login — good consolidation of the previously duplicated Promise.race blocks.
  • finishConnect now clears lastFailure through updateConnectionStatus({ lastFailure: undefined }) instead of a direct field mutation, so statusChange subscribers (e.g., the banner) correctly react to recovery.
  • hasAttachedOnce/hasAttached() plus the Retry-vs-reload branch in ConnectionErrorBanner looks correct, and recovery-action errors are now logged instead of being silently discarded.

Nice work — this addresses the concerns raised earlier. Thanks for the detailed re-verification notes too.

dylan-savage
dylan-savage previously approved these changes Jul 14, 2026
…restarts (#1555)

* fix(shell-ui): persist session in localStorage across tabs and restarts

The rr_ session token is a 90-day sliding server-side session; keeping it
in sessionStorage was the only reason every tab/restart forced re-auth.
Move to localStorage with one-time migration and cross-tab storage sync.

Fixes rocketride-ai/rocketride-saas#303

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

* fix(shell-ui): handle replaced sessions

* fix(shell-ui): reload tabs on token replacement

---------

Co-authored-by: Krish Garg <krishgarg@Krishs-MacBook-Pro.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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