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: [email protected]
    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!

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