Skip to content

[$175] [Sentry: APP-7AR] HybridApp Android: Fabric addViewAt "failed to insert view" still crashes after #101057 #104743

Open

Description

@mountiny

Sentry

https://expensify.sentry.io/issues/APP-7AR

Impact (snapshot at filing)

  • Users (total since first seen): 1,395
  • Events: 1,501
  • Users (last 14d, all versions): 254
  • Users (last 14d, production 9.5.13 to 9.5.16 builds): about 51. 9.5.13-0: 28, 9.5.15-1: 16, 9.5.14-0: 5, 9.5.16-0: 2
  • First seen: 2026-03-11
  • Last seen: 2026-10-22 (ongoing)
  • Platform: HybridApp Android
  • App version(s): every Android build from 9.5.5 to 9.5.16. The crash continues after the fix in Fix Android Fabric mount crashes from merged mounting transactions (APP-7AR / APP-H7A) #101057 shipped.
  • Affected screen: TabNavigator
  • Mechanism: chained, level fatal

Stack trace (top frames)

IllegalStateException: addViewAt: failed to insert view [N] into parent [M] at index 1
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:393)
Caused by: IndexOutOfBoundsException: index=1 count=0
  at android.view.ViewGroup.addInArray(ViewGroup.java:5639)
  at android.view.ViewGroup.addViewInner(ViewGroup.java:5526)
  at android.view.ViewGroup.addView(ViewGroup.java:5323)
  at com.facebook.react.views.view.ReactClippingViewManager.addView(ReactClippingViewManager.kt:36)
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:385)
  at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute(IntBufferBatchMountItem.kt:122)
  at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems(MountItemDispatcher.kt:250)

Suspected cause

Fabric tells a clipping view (removeClippedSubviews) to insert a child at index 1 while the native parent has no children. The patch from #101057 (react-native+0.86.0+015+fix-merged-transaction-mount-order.patch) fixed the mount order for merged transactions, but another path still sends a mount instruction with an index that the clipping view does not have.

Reproduction

Unknown. See the Sentry events linked from the Sentry issue.

Related

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @sobitneupane

Activity

  1. self-assigned this
    on Oct 22, 2026
  2. added
    ExternalAdded to denote the issue can be worked on by a contributor
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 22, 2026
  3. melvin-bot commented on Oct 22, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @sobitneupane (External)

  4. changed the title [-][Sentry: APP-7AR] HybridApp Android: Fabric addViewAt "failed to insert view" still crashes after #101057[/-] [+][$175] [Sentry: APP-7AR] HybridApp Android: Fabric addViewAt "failed to insert view" still crashes after #101057[/+] on Oct 22, 2026
  5. melvin-bot commented on Oct 22, 2026

    @melvin-bot
  6. MelvinBot commented on Oct 22, 2026

    @MelvinBot
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    HybridApp Android crashes when Fabric inserts a view at index 1 into a native parent that has no children.

    What is the root cause of that problem?

    The native view tree falls out of sync with Fabric's shadow tree. The parent's first child either never gets added, or something removes it without telling Fabric. When the next insert arrives at index 1, ViewGroup.addView throws IndexOutOfBoundsException and SurfaceMountingManager.addViewAt rethrows it as a fatal error.

    Two corrections to the issue description:

    • The parent is not a clipping view. The crash frame ReactClippingViewManager.kt:36 is the plain parent.addView branch, which runs only when removeClippedSubviews is off (ReactClippingViewManager.kt:29-37). Any plain View can be the parent.
    • TabNavigator is probably incidental. That route name was added on 2026-04-24, but the crash was first seen on 2026-03-11. Most narrow-layout screens now sit under it, so most events report it.

    The line numbers in the stack (385/393) show that the crashing builds already include +015, +016, and +045. Earlier patches each closed one source of the desync (+015 merged-transaction order, +046 differ flatten bug). Other sources still reach this path, for example a skipped insert when the child's ViewState is missing, or a child detached through the "View already has a parent" branch. Without Fabric mount logs from a failing session, I can't confirm which source is left.

    Code references
    • Upstream v0.86.0 addViewAt rethrows both IllegalStateException and IndexOutOfBoundsException as fatal: SurfaceMountingManager.kt:380-392
    • +015 explains the merged-transaction source and lists APP-7AR: details.md:112-116
    • +045/+046/+047 handle the related mount crashes (APP-K0G, APP-H7A): details.md:370-388
    • The batch can't be retried, so one throw kills the app. That is why +045 and +047 turned the sibling crashes into soft exceptions.

    What changes do you think we should make in order to solve the problem?

    Add a new React Native patch, react-native+0.86.0+048+soft-fail-addViewAt-index-out-of-bounds-APP-7AR.patch, to SurfaceMountingManager.addViewAt. It follows the same approach as +045 and +047:

    1. In the IndexOutOfBoundsException catch, read the parent's child count from the view group manager. This returns the correct count for clipping parents too.
    2. If the index is larger than that count, log a ReactNoCrashSoftException with the parent tag, parent class, index, and child count.
    3. Retry the insert at the end of the parent's children instead of throwing.
    4. Keep the fatal rethrow for any other case, so unrelated bugs still surface.
    5. Add a details.md entry. Link the upstream fix and say to drop the patch once React Native ships it.

    This matches the open upstream fix by a Meta engineer, Fix IndexOutOfBoundsException crash in SurfaceMountingManager.addViewAt (#57172). Our version uses the manager's child count, not the raw childCount, so it is also correct for clipping parents.

    Trade-offs: positions stay correct because Fabric sets each child's frame directly. Sibling draw order and accessibility order can be wrong until the parent re-renders. Later removes still work because they fall back to finding the child by tag. The soft-exception logs keep the desync visible so we can track its source.

    What alternative solutions did you explore? (Optional)

    • Skip the insert: this hides the child and makes the desync worse with every later insert.
    • Check the index before inserting: some managers under-report their child count, so a pre-check could move inserts that would have worked. Catching the exception changes only the path that crashes today.
    • Find and fix the remaining source: this is still worth doing, but it needs a repro or Fabric mount logs. The soft-exception logs from this patch would help.
    Related issues

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/104743) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.

  7. Abdallah-1551 commented on Oct 22, 2026

    @Abdallah-1551

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    HybridApp Android still crashes fatally in SurfaceMountingManager.addViewAt ("failed to insert view [N] into parent [M] at index 1", caused by IndexOutOfBoundsException: index=1 count=0). It happens on builds that already include the earlier mount-order patches (+015 and related). The crash affects about 50 production users per 14 days. We need to stop the fatal crash and keep enough diagnostics to find the remaining desync source.

    What is the root cause of that problem?

    • The native Android view tree is out of sync with Fabric's shadow/mount tree. Fabric emits INSERT(parent, child, index=1), but the native parent has 0 children. Child 0 was never attached or was detached outside Fabric's knowledge. ViewGroup.addView(child, 1) then throws IndexOutOfBoundsException.
    • SurfaceMountingManager.addViewAt rethrows that exception as a fatal IllegalStateException. A mount batch (IntBufferBatchMountItem) cannot be retried or partially rolled back, so one bad index kills the app.
    • The crash frame ReactClippingViewManager.kt:36 is the plain parent.addView(...) branch. It runs when removeClippedSubviews is off, so the parent is not necessarily a clipping view. TabNavigator is probably incidental, because most narrow-layout screens sit under it.
    • Earlier patches closed individual desync sources (merged-transaction order, the differ flatten bug). At least one other source remains, for example a skipped insert when the child's ViewState is missing, or a child detached through the "View already has a parent" branch. We cannot identify it without a repro or Fabric mount logs. The right fix now is to make this path non-fatal and observable, as +045 and +047 did for the sibling crashes.

    What changes do you think we should make in order to solve the problem?

    Add a new patch, patches/react-native/react-native+0.86.0+048+soft-fail-addViewAt-index-out-of-bounds-APP-7AR.patch, touching only SurfaceMountingManager.kt.

    1. In addViewAt, change the IndexOutOfBoundsException handler. It must use the view group manager's child count, not parentView.childCount, because clipping parents report a different count:
    } catch (e: IndexOutOfBoundsException) {
      val managerChildCount = viewGroupManager.getChildCount(parentView)
      if (index > managerChildCount) {
        // Native tree is behind Fabric's shadow tree. Recover instead of crashing.
        ReactSoftExceptionLogger.logSoftException(
            MOUNTING_MANAGER_TAG,
            ReactNoCrashSoftException(
                "addViewAt: index $index > childCount $managerChildCount for parent " +
                "[$parentTag] (${parentView.javaClass.simpleName}), child [$tag]. " +
                "Appending instead.", e))
        try {
          viewGroupManager.addView(parentView, view, managerChildCount)
          return
        } catch (retry: RuntimeException) {
          throw IllegalStateException(
              "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $index", e)
        }
      }
      throw IllegalStateException(
          "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $index", e)
    }
    1. Keep the fatal rethrow for every other case: index <= childCount, a failed retry, and non-index exceptions. Unrelated bugs still surface and are not masked.
    2. The soft exception carries the parent tag, parent class, requested index, actual child count, and child tag. Sentry then groups these so we can find the remaining desync source. Optionally add the surface ID and the parent's component name from ViewState.mCurrentProps if cheaply available.
    3. Add an entry to patches/react-native/details.md in the existing format: upstream issue/PR links (RN issue [Due for payment 2025-04-16] [$250] Scan – RBR is not displayed in LHN when editing an amount to be higher than the scanned amount #57017 and the open upstream fix for addViewAt IOOBE), the reason (APP-7AR), and "remove when React Native ships the upstream fix".
    4. Update the Sentry/GitHub follow-up. Keep APP-7AR open (or file a follow-up issue) to track the new soft-exception volume and fix the true desync source once logs identify it.

    Trade-offs: Fabric sets each child's layout directly, so position and size stay correct. Sibling z-order and accessibility order may be wrong until the parent re-renders. Later removeViewAt calls still work because they fall back to finding the child by tag. This is far better than a fatal crash.

    What alternative solutions did you explore? (Optional)

    • Skip the insert on out-of-bounds: rejected. The child would be invisible and the desync would grow with every later insert.
    • Pre-check index > childCount before every insert: rejected. Some view managers under-report their child count, so a pre-check could change inserts that work today. Catching the exception only changes the path that crashes today.
    • Fix the remaining desync source directly: the ideal fix, but not possible without a repro or Fabric mount logs. The soft-exception telemetry from this patch is the path to it.
    • Wait for the upstream fix: rejected. The crash is ongoing and the Expensify patch pipeline already handles this class of fix.

    Testing

    • Build check: run npm run patch/patch-package and confirm +048 applies cleanly on top of +015, +016, +045, +046, and +047. Build the HybridApp Android debug and release variants.
    • Forced desync unit/instrumented test: in a test harness or a temporary debug hook, create a plain ReactViewGroup with 0 children and call SurfaceMountingManager.addViewAt(parentTag, childTag, 1).
      • Expected: no crash, the child is appended at index 0, and a ReactNoCrashSoftException is logged.
      • For the negative case, use a manager whose addView throws for a valid index. Expected: the original fatal IllegalStateException is still thrown.
    • Clipping parent: repeat with removeClippedSubviews=true and confirm the manager's child count is used and the insert is recovered.
    • Regression pass on Android: navigate through the bottom tabs, open and close the RHP, merge transactions, switch workspaces, and rotate. There should be no visual or ordering regressions.
    • Post-release: monitor Sentry. The fatal APP-7AR event rate should drop to about zero, and the new soft exceptions should provide the data to find the remaining desync source.
  8. camesenin commented on Oct 22, 2026

    @camesenin

    Contributor details
    Your Expensify account email: camesenin@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0170d6b02e9440e604

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On HybridApp Android, the application crashes fatal in Fabric's mounting phase with IllegalStateException: addViewAt: failed to insert view [tag] into parent [parentTag] at index 1, caused by an underlying IndexOutOfBoundsException: index=1 count=0. This continues to crash users even after patch +015 (PR #101057).

    What is the root cause of that problem?

    In React Native Fabric architecture on Android (SurfaceMountingManager.kt:380-392), when mounting view mutations computed by the shadow tree:

    1. When Fabric attempts to insert a child view into a native parent via viewGroupManager.addView(parentView, view, index), if the native view tree is out of sync with Fabric's shadow tree (e.g. index 1 is requested but the parent currently has count 0 because an earlier child was dropped, delayed, or removed by a layout pass), Android's ViewGroup.addView throws IndexOutOfBoundsException.
    2. In SurfaceMountingManager.kt:
      } catch (e: IndexOutOfBoundsException) {
        throw IllegalStateException(
            "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $index", e)
      }
      SurfaceMountingManager.addViewAt catches IndexOutOfBoundsException and unconditionally re-throws it as a fatal IllegalStateException, causing an immediate app crash for the user.
    3. While patch +015 resolved transaction-mount ordering and patch +046 resolved nested flatten bugs, subtle race conditions in Android's native view hierarchy still trigger out-of-bounds indices.
    4. Expensify has already established the proven pattern for sibling Fabric mounting crashes (APP-K0G in patch +045 and APP-H7A in patch +047): rather than terminating the application fatally for recoverable layout hierarchy discrepancies, the engine should log a ReactNoCrashSoftException and safely append the view at the current managerChildCount.

    What changes do you think we should make in order to solve the problem?

    Introduce patch react-native+0.86.0+048+soft-fail-addViewAt-index-out-of-bounds-APP-7AR.patch modifying SurfaceMountingManager.kt:
    When IndexOutOfBoundsException occurs and index > managerChildCount:

    1. Use viewGroupManager.getChildCount(parentView) (which correctly accounts for clipping managers like ReactClippingViewManager as well as standard ReactViewGroup).
    2. Log a ReactNoCrashSoftException via ReactSoftExceptionLogger containing parentTag, parentView class name, requested index, actual childCount, and child tag.
    3. Recover gracefully by appending the child at managerChildCount instead of throwing a fatal exception.
    4. Keep the fatal rethrow strictly for unrecoverable errors (e.g. if the fallback append itself fails).
    --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt
    +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt
    @@ -385,8 +385,25 @@ internal constructor(
         } catch (e: IllegalStateException) {
           throw IllegalStateException(
               "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $index", e)
         } catch (e: IndexOutOfBoundsException) {
    +      val managerChildCount = viewGroupManager.getChildCount(parentView)
    +      if (index > managerChildCount) {
    +        ReactSoftExceptionLogger.logSoftException(
    +            TAG,
    +            ReactNoCrashSoftException(
    +                "addViewAt: index $index > childCount $managerChildCount for parent [$parentTag] (${parentView.javaClass.simpleName}), child [$tag]. Appending instead.",
    +                e,
    +            ),
    +        )
    +        try {
    +          viewGroupManager.addView(parentView, view, managerChildCount)
    +          return
    +        } catch (retry: RuntimeException) {
    +          throw IllegalStateException(
    +              "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $index", e)
    +        }
    +      }
           throw IllegalStateException(
               "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $index", e)
         }

    Add an entry to patches/react-native/details.md documenting:

    Why this solution is optimal:

    1. Direct parity with established architectural patterns: Mirrors patches +045 and +047 in patches/react-native/details.md.
    2. Eliminates fatal crashes in production: Soft-logging keeps the app running smoothly for users while delivering telemetry to Sentry to trace the exact root trigger.
    3. Correct clipping support: Using viewGroupManager.getChildCount(parentView) guarantees accuracy across all view manager types.

    What alternative solutions did you explore? (Optional)

    • Dropping/skipping the insert entirely:
      Rejected. Skipping the child leaves UI elements unmounted and missing from the screen. Appending at managerChildCount mounts the view while Fabric's explicit layout properties position and size it correctly.
    • Pre-checking index > childCount before every addView:
      Rejected. Some custom view managers dynamically manage their child list; adding an eager pre-check could disrupt valid mounts. Catching the specific exception guarantees that only currently-crashing paths are altered.

    Testing

    1. Run npm run patch to ensure patch applies cleanly over +015, +045, +046, and +047.
    2. Build Android debug APK and verify successful compilation.
    3. Regression test bottom tab navigation, switching workspaces, and chat thread scrolling on Android to verify zero visual regressions.
  9. melvin-bot commented on Oct 22, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  10. xian96xx commented on Oct 25, 2026

    @xian96xx

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On HybridApp Android (React Native 0.86 / Fabric), Sentry issue APP-7AR (java.lang.IllegalStateException: Failed to execute: ... SurfaceMountingManager.addViewAt) continues to crash in production (v9.3.39-2 / v9.4.1-2) even after PR #101057 added react-native+0.86.0+045+soft-fail-addViewAt-non-ViewGroup-parent-APP-K0G.patch to soft-fail when parentView.view is not a ViewGroup.

    When parentView.view is a valid ViewGroup (ReactViewGroup), SurfaceMountingManager.addViewAt still throws a fatal IndexOutOfBoundsException inside getViewGroupManager(parentViewState).addView(parentView, view, index) (for example index=1 count=0 or index=2 count=1), which IntBufferBatchMountItem.execute wraps in an uncaught IllegalStateException.

    What is the root cause of that problem?

    In packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt (around lines 318–408), addViewAt(parentTag, tag, index) computes the target index from C++ ShadowTree diffing (IntBufferBatchMountItem), where index assumes parentView's current Android child count matches the shadow tree's precedent sibling count.

    However, in SurfaceMountingManager.kt on RN 0.86 (combined with our existing soft-fail patches 015, 016, 045, and 047), there are three concrete paths where the actual child count managed by getViewGroupManager(parentViewState) is smaller than index at the moment getViewGroupManager(parentViewState).addView(parentView, view, index) runs:

    1. Same-parent re-insertion inside addViewAt itself (SurfaceMountingManager.kt:350-378):
      When view.parent != null, patch 015 soft-logs a ReactNoCrashSoftException and lines 374–377 detach view from its existing viewParent via viewParent.endViewTransition(view) and viewParent.removeView(view). When viewParent === parentView (i.e. view was already attached to parentView at an earlier index such as 0 when parentView had 1 child, and a merged/reordered mount item re-inserts it at index = 1), viewParent.removeView(view) decrements parentView's child count from 1 to 0 immediately before line 381 calls addView(parentView, view, 1)—deterministically throwing IndexOutOfBoundsException: index=1 count=0.
    2. Preceding sibling Insert skipped in the same IntBufferBatchMountItem (SurfaceMountingManager.kt:333-340, plus patch 045):
      If child 0's ViewState was already deleted (getNullableViewState(tag) == null at line 333) or skipped by a soft-fail guard, addViewAt(parentTag, child0Tag, 0) returns early without adding a child to parentView. The very next instruction in the same IntBufferBatchMountItem (addViewAt(parentTag, child1Tag, 1)) still carries index = 1 from the shadow diff while parentView has 0 children, throwing IndexOutOfBoundsException: index=1 count=0.
    3. Out-of-band / asymmetric removeViewAt (SurfaceMountingManager.kt:469-482):
      removeViewAt already tolerates index mismatches and already-removed children by logging a ReactNoCrashSoftException and clamping/decrementing backward instead of crashing, whereas addViewAt passes the unvalidated index straight into getViewGroupManager(parentViewState).addView(parentView, view, index) and rethrows any exception inside catch (e: IllegalStateException) / uncaught IndexOutOfBoundsException.

    What changes do you think we should make in order to solve the problem?

    We should add a new React Native patch patches/react-native/react-native+0.86.0+048+soft-fail-addViewAt-index-out-of-bounds-APP-7AR.patch (and document it in patches/react-native/details.md) that updates SurfaceMountingManager.addViewAt in packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt:

    1. Query child count through getViewGroupManager(parentViewState).getChildCount(parentView) (not parentView.childCount directly):
      In SurfaceMountingManager.kt, child insertion and lookup are delegated through IViewGroupManager (getViewGroupManager(parentViewState).addView(parentView, view, index) at line 381, viewGroupManager.getChildAt(...) at line 456, viewGroupManager.getChildCount(parentView) at line 521) because custom IViewGroupManager implementations may wrap or virtualize children such that viewGroupManager.getChildCount(parentView) differs from raw parentView.childCount.
    2. Clamp index after any viewParent.removeView(view) detach and log ReactNoCrashSoftException(TAG, ...):
      Right before calling viewGroupManager.addView(parentView, view, safeIndex) (after the if (viewParent != null) block at lines 351–378 has already detached view if needed), check val childCount = viewGroupManager.getChildCount(parentView). If index < 0 || index > childCount, log a ReactNoCrashSoftException via ReactSoftExceptionLogger.logSoftException(TAG, ...) (using the class's existing TAG constant) with diagnostic context (tag, parentTag, requested index, actual childCount), and clamp val safeIndex = index.coerceIn(0, childCount).
    3. Catch IndexOutOfBoundsException as defense-in-depth:
      As noted in SurfaceMountingManager.kt:506-520, some custom ViewManager implementations override getChildCount inconsistently with their internal addView. If viewGroupManager.addView(parentView, view, safeIndex) still throws IndexOutOfBoundsException, catch it and log a ReactNoCrashSoftException with TAG instead of crashing the app.
          val viewGroupManager = getViewGroupManager(parentViewState)
          val childCount = viewGroupManager.getChildCount(parentView)
          val safeIndex =
              if (index < 0 || index > childCount) {
                ReactSoftExceptionLogger.logSoftException(
                    TAG,
                    ReactNoCrashSoftException(
                        "Unable to insert view [$tag] into parent [$parentTag] at index $index (childCount=$childCount); clamping to ${index.coerceIn(0, childCount)}"))
                index.coerceIn(0, childCount)
              } else {
                index
              }
    
          try {
            viewGroupManager.addView(parentView, view, safeIndex)
          } catch (e: IndexOutOfBoundsException) {
            ReactSoftExceptionLogger.logSoftException(
                TAG,
                ReactNoCrashSoftException(
                    "Failed to addView [$tag] into parent [$parentTag] at safeIndex $safeIndex (requestedIndex=$index, childCount=$childCount)",
                    e))
          } catch (e: IllegalStateException) {
            throw IllegalStateException(
                "addViewAt: failed to insert view [$tag] into parent [$parentTag] at index $safeIndex", e)
          }

    What alternative solutions did you explore? (Optional)

    • Using parentView.childCount directly: SurfaceMountingManager inserts through getViewGroupManager(parentViewState).addView(parentView, view, index), not parentView.addView(view, index). Using viewGroupManager.getChildCount(parentView) matches removeViewAt (SurfaceMountingManager.kt:521) and respects custom IViewGroupManager implementations, while the catch (e: IndexOutOfBoundsException) block guards against custom managers whose getChildCount and addView diverge.
    • Skipping the insert instead of clamping to [0, childCount]: Dropping the view entirely leaves the mounted view subtree blank even when the child ViewState is valid; clamping to childCount appends the view in its parent ViewGroup so it remains visible and interactive while reporting the non-fatal invariant violation to Sentry/soft-exception logs.
  11. melvin-bot commented on Oct 25, 2026

    @melvin-bot

    📣 @xian96xx! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  12. sobitneupane commented on Oct 26, 2026

    @sobitneupane
    Contributor

    @mountiny I could not prioritize this issue. Could you please reassign it?

  13. shilo1979jk commented on Oct 26, 2026

    @shilo1979jk

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    HybridApp Android still terminates with SurfaceMountingManager.addViewAt: failed to insert view after #101057. The supplied cause is IndexOutOfBoundsException: index=1 count=0: Fabric requests an insertion beyond the native parent's current children. We need bounded recovery for this invalid insertion while preserving diagnostics and avoiding duplicate or silently missing views.

    What is the root cause of that problem?

    The proven failure is a disagreement between a Fabric insertion index and the native child list, followed by an unconditional fatal rethrow. In React Native 0.86.0's SurfaceMountingManager, addViewAt delegates to the view manager and wraps IndexOutOfBoundsException in IllegalStateException. Nothing repairs this index mismatch before it escapes the mount batch.

    Two details matter:

    There are concrete ways to reach the remaining mismatch: an earlier insertion can return because its child ViewState is absent; the existing-parent recovery can detach a child before reinsertion, reducing the target count when both parents are the same. Both are testable fault scenarios. Neither is established as the production trigger by this stack. TabNavigator identifies the active route, not necessarily the component that caused the mismatch. Without the preceding mount operations, the originating desynchronization remains unknown.

    What changes do you think we should make in order to solve the problem?

    Add the next available ordered React Native patch for APP-7AR, documented in patches/react-native/details.md. Change only the out-of-bounds recovery path in addViewAt, building on upstream PR #57172, which is still open at review time, with these safeguards:

    1. Attempt the requested insertion first. Keep valid inserts and existing IllegalStateException handling unchanged. Do not pre-clamp every index: RN explicitly warns that custom managers can report counts that differ from their actual insertion behavior.

    2. In the IndexOutOfBoundsException catch, obtain the same viewGroupManager used for insertion and read getChildCount(parentView) after any existing-parent detachment. A clipping manager counts logical children, including clipped ones; raw parentView.childCount can be smaller and choose the wrong position.

    3. Recover only when the requested index is nonnegative and greater than a nonnegative manager count. Before retrying, require that the failed insert left the child detached and absent from the manager's logical child list. A custom manager may throw after partially attaching/registering a child; blindly retrying could duplicate it. If the child is already present or state cannot be established safely, preserve the contextual failure rather than silently drop an insertion. If the count/child inspection itself throws, retain that diagnostic alongside the original exception and do not attempt another insertion. Negative indices and failures at otherwise valid indices are outside this recovery.

    4. Retry once, appending at that manager count. On success, log a ReactNoCrashSoftException with a stable APP-7AR recovery prefix and context: surface ID, parent/child tags and classes, requested index, manager/raw counts and the child's original parent before detachment. Retain the original exception as the cause. These details distinguish missing siblings from same-parent reinsertion without collecting user content.

    5. If the append fails, throw a contextual exception whose cause is the retry failure, retaining the first insertion failure as suppressed context. Do not catch every runtime error and report only the original exception: that loses why recovery failed. Do not continue the batch as if a child was inserted when both attempts failed.

    This is a narrowly scoped crash mitigation, not proof that Fabric's original tree divergence is fixed. Appending keeps the valid child mounted in the recoverable case, but sibling drawing/accessibility order may differ; layout, visibility and subsequent mount operations must be verified rather than assumed correct. Follow both recovered and residual fatal events after rollout to locate the upstream cause.

    Validation: apply the patch on top of the actual installed 0.86.0 patch sequence and build HybridApp Android. Extend the upstream SurfaceMountingManagerTest approach with an empty parent/index 1, a nonempty parent/oversized index, same-parent detach/reinsert and a missing preceding child. Assert the child is present exactly once, a soft exception is emitted, and subsequent insert/remove operations target the right tags. Cover clipping with logical versus attached counts, a valid insert despite an under-reporting manager, partial-mutation failure, negative index, valid-index failure and a failed fallback retaining both exceptions. Exercise tabs, RHP navigation, workspace changes and transaction merging.

    What alternative solutions did you explore? (Optional)

    • Unconditional pre-clamping: changes successful custom-manager behavior and can conceal a negative-index bug.
    • Catch and return, including after a failed append: prevents one crash by leaving a valid child missing and the rest of the batch operating on another divergent tree.
    • Copy upstream's raw-child-count fallback verbatim: its recovery direction is useful, but manager-aware indexing, partial-attachment checks and preserving the retry cause matter for a safe backport.
    • Disable clipping, blame TabNavigator, or repeat +015/+045: the supplied stack does not justify those changes. Fixing a newly identified differ/mount-order cause would be preferable once the preceding operations establish one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions