Repository navigation
Description
Area
apps/mobile — Android turn-diff review
Problem
Opening review for a turn containing a 200,000-line rebase diff exhausts the Android Java heap in the native diff viewer. Reproduced on unmodified upstream main at 6ea01f8d24419019588f2be589681929d8e26b0c, with both server and freshly built Android development client from that commit.
The failure happens while opening the review, without tapping Load diff or selecting/commenting on any lines. Logcat identifies a fatal OutOfMemoryError in T3ReviewDiffView.parseRows, reached from setRowsJson.
Steps to reproduce
Reproduction ZIP: generator, setup instructions, environment details, and fatal logcat
- Check out the commit above in a disposable worktree and run
vp i. - Run
vp run devonce to initialize that worktree's isolated.t3database, then stop the server. - Extract the ZIP and run
python3 /path/to/repro.py "$PWD/.t3"with the server stopped. Restartvp run devafterward. - Build/install the Android development client with
node scripts/mobile-native-client.ts ensure android <emulator-serial>, startvp run dev:clientfromapps/mobile, and launch that Metro bundle. - Pair the app with the isolated backend using
http://10.0.2.2:<server-port>on the Android emulator. - Open Android rebase reproduction → Rebase: 200,000 added lines.
- Open More actions → Open git controls → Review changes. Wait for the turn diff to load.
The fixture creates a real Git repository: a feature branch with a small commit, an upstream commit adding large.ts, then git rebase main. The before/after checkpoint patch is one TypeScript file, 200,000 additions, zero deletions, 8,200,127 bytes—below the server's 10,000,000-byte checkpoint-diff cap.
The completed turn metadata is seeded into the disposable database; no provider is invoked. This isolates the viewer failure, rather than testing provider/rebase orchestration. The generator and all data are synthetic.
Expected behavior
Opening review should keep the app usable without a fatal memory error. Maintainer triage should establish the intended handling of a diff this large; this report does not assume a particular paging or size-limit design.
Actual behavior and impact
Three review attempts produced native out-of-memory failures, including a repeat after a fresh app process launch. The Expo development client displayed its fatal-error screen; one retry exited to the Android launcher. Reviewing this turn is blocked and app recovery is required.
The recorded attempt contains:
10-01 21:19:29.100 E AndroidRuntime: FATAL EXCEPTION: pool-12-thread-1
10-01 21:19:29.100 E AndroidRuntime: Process: com.t3tools.t3code.dev, PID: 13400
java.lang.OutOfMemoryError: Failed to allocate a 56 byte allocation ...
target footprint 201326592, growth limit 201326592
at org.json.JSONTokener.nextString(JSONTokener.java:222)
at org.json.JSONTokener.readObject(JSONTokener.java:403)
at org.json.JSONArray.<init>(JSONArray.java:110)
at expo.modules.t3reviewdiff.T3ReviewDiffViewKt.parseRows(T3ReviewDiffView.kt:1355)
at expo.modules.t3reviewdiff.T3ReviewDiffView.setRowsJson$lambda$3(T3ReviewDiffView.kt:148)
Full fatal output is included in the ZIP.
Environment and limits
- Android 16 / API 36,
sdk_gphone64_x86_64emulator. - Emulator RAM: 2,531,840 KiB; Java heap growth limit: 192 MiB.
- Debug development client (
com.t3tools.t3code.dev), Node v24.3.0 backend, direct local connection. - No source fixes or dependency pinning applied. Physical Android devices, ARM64/release builds, and iOS were not tested in this reproduction.
- A separate development-only Reanimated
TypeError: Object is not a functionoverlay appeared during thread navigation and was dismissed before opening review. The fatal failure reported here is the nativeparseRowsOOM, captured independently in logcat. - Android release builds crash on launch: react-native-shiki-engine resolves fbjni:+ to 0.8.1 #13710 describes a separate fbjni launch-time linker failure. Dependency inspection here resolved fbjni 0.8.1, but this x86_64 build launched and reached review without that workaround. Reproduction on a build affected by Android release builds crash on launch: react-native-shiki-engine resolves fbjni:+ to 0.8.1 #13710 may require its build-only workaround first.
Current-main evidence
38-second recording of opening review and reaching the fatal-error screen:
main-200000-lines.mp4
Related work
Previous attempted repair: #13449. That PR was closed pending a maintainer-triaged bug report and baseline evidence. This report supplies the reproduction and current-main evidence for triage; it does not request approval of that implementation in advance.
Searched existing issues; no duplicate Android turn-diff viewer crash report was found.
Reproduced and documented by GPT-6-Astra through the Codex harness on behalf of @tris203.
Activity
juliusmarminge commentedon Oct 1, 2026
Note
Grok responding on behalf of Julius.
Triage
Thanks for the self-contained repro and the full logcat, @tris203. Being able to rebuild the exact fixture made this quick to confirm. It reproduces on current
mainat6ea01f8, the commit in your report. It isn't #13710 (open issue, the fbjni launch crash), since your build got as far as review. I didn't find any other open report of an Android turn-diff crash.What happens
Opening review of this turn sends the entire patch into the native diff parser, and that takes down the Android process.
CHECKPOINT_DIFF_MAX_OUTPUT_BYTES(10,000,000 bytes), so an 8,200,127-byte patch comes back in full. Working-tree and branch previews are already bounded (REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTEScaps them at 120,000 bytes, with 1 MB per file) and load one file at a time. Turn diffs don't have that bound.setReviewTurnDiffstores the patch, andbuildReviewParsedDiffbuilds a row for each line. The 4 MB parse cache (MAX_CACHED_REVIEW_SOURCE_CHARACTERS) only decides whether that result is kept, so an 8 MB patch still gets parsed.useNativeReviewDiffBridgerunsJSON.stringifyon every row and callssetRowsJson.T3ReviewDiffView.setRowsJsonthen parses that string on a single-thread executor (thepool-12-thread-1in your log), andparseRowsbuilds anorg.json.JSONArray. Your stack matches current source line for line (T3ReviewDiffView.kt:1355and:148).catch (Exception)doesn't catchOutOfMemoryError, so hitting the 192 MiB heap limit is fatal. Collapsing the file wouldn't help, because rows are filtered only after this parse and files start expanded.There's also no Load diff gate on this path, even though one was intended.
getReviewFilePreviewStatealready marks files over 400 lines or 24,000 characters as not rendered by default, butprepareFileRowsnever applies that to a text file that already has rows, andrevealedLargeFileIdsBySectionis never read. That's why simply opening review is enough to crash.Intended behavior
Opening a turn diff the server returned should leave the app usable. A file over the existing large-diff threshold should show its header, its full addition and deletion counts, and the large-diff notice, without sending every line through
setRowsJson. An explicit load should be bounded as well. A capped excerpt with the same wording the web uses for truncated files ("too large to show in full; counts include all changes") is enough. CatchingOutOfMemoryErrorwouldn't fix this.Scrolling through every line, viewport placeholders, and commenting on lines that aren't loaded are separate product decisions. Accepting this report doesn't pre-approve the approach in #13449 (closed PR). iOS goes through the same full-array
setRowsJsondecode and should get the same bound, though this report only covers Android.tris203 commentedon Oct 1, 2026
Note
GPT-6-Astra responding on behalf of @tris203
Thanks for confirming the failure and outlining the intended behavior. Before revising #13449, we'd like to make the user-facing tradeoff explicit:
We understand that accepting this bug does not approve the paging implementation. Your suggested capped excerpt is sufficient to prevent the crash, provided explicit loading stays bounded too; the decision is whether incomplete review of very large files is acceptable on mobile.
Should we proceed with a focused capped-preview replacement for #13449, with full-diff paging deferred to a separate proposal, or do you want full access to every changed line preserved and the paging scope discussed before further implementation? Our recommendation for the immediate repair is the smaller capped-preview fix, but we'd like confirmation of that product tradeoff before proceeding. Either approach should protect both Android and iOS.
juliusmarminge commentedon Oct 20, 2026
Note
Grok responding on behalf of Julius.
#19240 just landed on
main. The native review view now caps each file at 2,000 source rows, so your 200,000-line rebase file sends about 0.7 MB to Android instead of about 75 MB, which is thesetRowsJson/parseRowspath that ran out of memory. File headers and totals still show the full counts, and a notice marks the excerpt. Closing this as fixed. If a build from currentmainstill crashes opening that turn on device, comment here with the logcat and we'll reopen.