Skip to content

Fix the infinite redirect loop for boards#177

Draft
Rajat-Dabade wants to merge 1 commit intomainfrom
final-infinite-redirect
Draft

Fix the infinite redirect loop for boards#177
Rajat-Dabade wants to merge 1 commit intomainfrom
final-infinite-redirect

Conversation

@Rajat-Dabade
Copy link
Contributor

@Rajat-Dabade Rajat-Dabade commented Mar 17, 2026

Summary

Findings on the Infinite Redirect Issue for Boards

While trying to reproduce the infinite redirect issue in Boards, I noticed a pattern related to URLs that end with /error.
When the page is reloaded while the URL contains /error, the following sequence happens:

  • Initial URL
    Example URL:
http://localhost:8065/boards/team/<teamId>/<boardId>/<viewId>/error
  • Error boundary triggers
    The application error boundary catches an error and runs this redirect:
window.location.replace('plugins/focalboard/error?id=unknown')
  • Browser treats the redirect as a relative path
    Since the path doesn’t start with /, the browser appends it to the current URL instead of treating it as a root path.
    So the new URL becomes:
http://localhost:8065/boards/team/<teamId>/<boardId>/<viewId>/plugins/focalboard/error?id=unknown
  • Route mismatch happens
    This new URL does not match the /error route, so the app loads BoardPage (or a similar route) with incorrect parameters.

  • Another error is triggered
    BoardPage fails again, which causes:

  • another error

  • the error boundary to run again

  • another redirect

  • URL keeps growing
    Each redirect appends more segments to the path, such as:

.../plugins/focalboard/error
.../boards/error
.../boards/plugins/focalboard/error

This creates the infinite redirect loop.

Why the error boundary kept triggering

Routes like:

/boards/team/.../error

are matched by the BoardPage route:

/team/:teamId/:boardId?/:viewId?/:cardId?

In this case: cardId = "error"

BoardPage then tries to load a card with ID “error”, which does not exist.
This throws an error, which the error boundary catches again, restarting the same redirect process.

Fix

The component can be added so it runs on initial load and, when the path ends with /error (or corrupted variants), redirects to the clean path instead of letting the app hit the error boundary with a bad URL. That breaks the loop before it can start.

Before:

After:

Ticket Link

https://mattermost.atlassian.net/browse/MM-65641

Change Impact: 🟡 Medium

Reasoning: The changes introduce a new error path fixing component that runs on initial load to prevent redirect loops in board navigation. While the modification is localized to the router layer and addresses a specific infinite loop issue, it affects core routing logic and error boundary behavior that touches user-facing critical paths (board loading). The changes handle a delicate flow involving URL rewriting and redirect interception.

Regression Risk: The InitialLoadErrorPathFix component runs on every initial load and rewrites URLs ending with /error, which could potentially interfere with legitimate error page access or other routes that coincidentally end with /error. The enhancement to GlobalErrorRedirect adds location-dependent logic for redirect decisions, which introduces additional state-based behavior that could have edge cases. There is moderate risk of affecting error handling flows in unexpected ways if edge cases in the error path clearing are not fully covered.

QA Recommendation: Comprehensive manual QA is recommended, specifically testing: (1) board loading flows from various entry points (direct URLs, navigation from boards list, shared links); (2) error boundary triggering and error page display under various failure conditions; (3) URLs that legitimately end with /error or contain /error in their paths; (4) navigation with query parameters to ensure they are preserved; (5) the clean redirect path behavior on cold loads versus navigations. Given this touches error handling and routing—critical paths that affect user experience—skipping manual QA poses notable risk.

@Rajat-Dabade Rajat-Dabade self-assigned this Mar 17, 2026
@Rajat-Dabade Rajat-Dabade added 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester labels Mar 17, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

The pull request introduces a new InitialLoadErrorPathFix component that rewrites URLs on initial page load to remove trailing /error segments, preventing redirect loops. It also enhances GlobalErrorRedirect to be location-aware for safer error page handling, reducing infinite redirect scenarios.

Changes

Cohort / File(s) Summary
Router Error Handling Enhancement
webapp/src/router.tsx
Added InitialLoadErrorPathFix component for initial load URL rewriting to remove /error trailing segments; integrated into router pipeline. Enhanced GlobalErrorRedirect with location dependency for safer, context-aware error redirects.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Router
    participant InitialLoadErrorPathFix
    participant GlobalErrorRedirect
    
    Browser->>Router: Initial page load (e.g., /some-path/error)
    Router->>InitialLoadErrorPathFix: Render component on first mount
    InitialLoadErrorPathFix->>InitialLoadErrorPathFix: Check if first load and path has trailing /error
    alt Path has trailing /error (not already at /error)
        InitialLoadErrorPathFix->>Browser: Rewrite URL to remove /error segment
        Browser->>Router: Navigate to rewritten path
    end
    Router->>GlobalErrorRedirect: Check for global error state
    alt Global error exists and not on error page
        GlobalErrorRedirect->>GlobalErrorRedirect: Clear error and prepare redirect
        GlobalErrorRedirect->>Browser: Redirect to /error or /access-denied
    else On error page or no global error
        GlobalErrorRedirect->>Router: Continue normal rendering
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix the infinite redirect loop for boards' directly and clearly addresses the main objective of the PR, which is to resolve an infinite redirect loop issue in the boards feature.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch final-infinite-redirect
📝 Coding Plan
  • Generate coding plan for human review comments

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

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

@pvev
Copy link
Contributor

pvev commented Mar 17, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

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.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webapp/src/router.tsx`:
- Around line 191-195: The current loop only strips trailing "/error" segments
(using validPath.endsWith('/error')), which fails for leaked basename suffixes
like "/team/.../error/plugins/focalboard/error"; change the normalization in the
router (variables: pathname, validPath, history.replace) to repeatedly remove
any "/error" segment and everything after it instead of only trimming a trailing
"/error"—e.g., while validPath.includes('/error') and validPath !== '/error',
set validPath = validPath.slice(0, validPath.indexOf('/error')) || '/'—then call
history.replace(validPath + location.search) so all leaked "/error" basename
suffixes are normalized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d8ab5086-3aba-47a9-80cd-3207a31e89a7

📥 Commits

Reviewing files that changed from the base of the PR and between bc67146 and dce93d6.

📒 Files selected for processing (1)
  • webapp/src/router.tsx

Comment on lines +191 to +195
let validPath = pathname
while (validPath.endsWith('/error') && validPath !== '/error') {
validPath = validPath.replace(/\/error$/, '') || '/'
}
history.replace(validPath + location.search)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize leaked basename suffixes, not just trailing /error.

At Line [193], the loop strips only /error. For corrupted URLs like /team/.../error/plugins/focalboard/error, this can stop at /team/.../error/plugins/focalboard, which is still invalid.

Proposed fix
     useEffect(() => {
         if (hasRun.current) return
         hasRun.current = true

         const pathname = location.pathname
+        const leakedBase = Utils.getFrontendBaseURL().replace(/^\/+|\/+$/g, '')
+        const escapedBase = leakedBase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+        const basePart = leakedBase ? `${escapedBase}/` : ''
+        const trailingErrorPattern = new RegExp(`/(?:${basePart})?error$`)
+
         if (pathname === '/error' || pathname === '/access-denied') return
-        if (!pathname.endsWith('/error')) return
+        if (!trailingErrorPattern.test(pathname)) return

         let validPath = pathname
-        while (validPath.endsWith('/error') && validPath !== '/error') {
-            validPath = validPath.replace(/\/error$/, '') || '/'
+        while (trailingErrorPattern.test(validPath) && validPath !== '/error') {
+            validPath = validPath.replace(trailingErrorPattern, '') || '/'
         }
         history.replace(validPath + location.search)
     }, [location.pathname, location.search, history])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/src/router.tsx` around lines 191 - 195, The current loop only strips
trailing "/error" segments (using validPath.endsWith('/error')), which fails for
leaked basename suffixes like "/team/.../error/plugins/focalboard/error"; change
the normalization in the router (variables: pathname, validPath,
history.replace) to repeatedly remove any "/error" segment and everything after
it instead of only trimming a trailing "/error"—e.g., while
validPath.includes('/error') and validPath !== '/error', set validPath =
validPath.slice(0, validPath.indexOf('/error')) || '/'—then call
history.replace(validPath + location.search) so all leaked "/error" basename
suffixes are normalized.

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

Labels

2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants