You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Exploratory] WS settings - App crashes after importing a multi level tag file #100367
Version Number: 9.4.70-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:https://test-management.browserstack.com/projects/2219752/folder/53774965/test-cases/41237320 Email or phone of affected tester (no customers):gatantm+681@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: iPhone 12 mini, iOS 16.7 App Component: Workspace Settings
Action Performed:
Launch new expensify
Tap on Workspaces and open a workspace with tags enabled
Tap on Tags
Tap On more and Import spreadsheet
Tap on Choose File and select the attached file
Tap on Import
Expected Result:
App is back to the tags list with imported tags available
The app aborts on an uncaught Objective-C NSRangeException from -[NSData subdataWithRange:] inside RCTBlobManager, on the com.meta.react.turbomodulemanager.queue background queue. Because the method is void/async, React Native re-throws the exception out of the dispatch block, so no JS frame can catch it and the process calls abort().
Multi-level tags is the only spreadsheet importer that re-uploads the raw file instead of posting parsed JSON, so it is the only import flow that builds a blob:
src/libs/fileDownload/FileUtils.ts:294 does new File([blob], …), which wraps one blob as a part of another. That is the only call that reaches resolve:offset:size: → subdataWithRange:.
The slice overruns because the JS-side blob descriptor records more bytes than native actually stored. JS counts the string's full UTF-8 byte length, while the NSString built from the JSI string is shorter — so subdataWithRange:{0, size} reads past the end.
Why this particular file triggers it: the attached CSV is 327 bytes, contains zero NUL bytes, and starts with two back-to-back UTF-8 BOMs (plus a third inside the quoted "Department GL" header):
00000000 ef bb bf ef bb bf 44 65 70 61 72 74 6d 65 6e 74 |......Department|
00000010 2c 22 ef bb bf 44 65 70 61 72 74 6d 65 6e 74 20 |,"...Department |
Foundation's UTF-8 decoder drops a leading BOM, giving native 324 bytes against a JS descriptor of 327 — a 3-byte shortfall, matching the Sentry message shape (range {0, 321} exceeds data length 318). This matters for triage: the earlier APP-KDN work attributed the desync to embedded NUL bytes, which this file does not have. The BOM is a second, distinct producer of the same desync.
What changes do you think we should make in order to solve the problem?
Clamp the range to what is really here instead of letting subdataWithRange: raise an NSRangeException, which is fatal on this background queue because no JS frame can catch it.
During that retest, check the imported tag names. Nothing in this flow strips the BOM — the CSV goes to the backend byte-for-byte — so the created tag lists may be named with an invisible U+FEFF prefix. That would be a separate, cosmetic follow-up.
Two independent checks support the RCTBlobManager mapping:
The three React offsets sort in the same order as the three methods appear in RCTBlobManager.mm (resolve: at :101 < resolve:offset:size: at :108 < createFromParts:withId: at :199/:213).
The -[NSConcreteData copyWithZone:]+0 symbol is a mis-symbolication: it is an adjacent NSData concrete-cluster method in Foundation's __TEXT, and symbolLocation: 0 indicates the symbolicator used a mismatched Foundation build for iOS 16.7 (20H19).
Byte verification of the attached CSV: 327 bytes total; stripping NUL bytes leaves 327 bytes, so there are none. Three EF BB BF sequences, two at offset 0.
Timeline: crash captured 2026-09-04 on 9.4.71-0 → PR #101062 merged 2026-09-18 → deployed to production in 9.4.90-2 on 2026-09-21. The reported build predates the fix.
Alternatives considered and ruled out:
A JS-level error in the tags import. Ruled out — the crash is a native SIGABRT with no JS frames, and the App-side spreadsheet code (ImportSpreadsheet.ts, ImportSpreadsheetColumns.tsx) uses .at() with ?? '' fallbacks throughout, with no non-null assertions.
A third-party native module (document picker, blob-util, nitro-sqlite). Ruled out — the Podfile links those statically into the Expensify binary (image index 0), but the crashing frames are in React.framework (index 12). The nitro-thread-4 thread sitting in sqlite3_step is unrelated background Onyx I/O.
NSInvalidArgumentException from NSJSONSerialization or base64 decoding. Ruled out by the symbolicated APP-KDN stack: the raiser is subdataWithRange: with a range-exceeds-length message.
A legacy (non-TurboModule) bridged module. Ruled out — the new architecture is enabled (ios/Podfile:95, RCT_NEW_ARCH_ENABLED=1) and every ObjC TurboModule is invoked via NSInvocation, so that frame pattern is expected for RCTBlobManager, which is a real TurboModule.
Not verified: I did not reproduce on an iOS device. The BOM mechanism specifically — that Foundation drops a leading BOM during the JSI-string-to-NSString conversion — is inferred from the byte counts and has not been confirmed on device. The crash identification itself does not depend on it: the clamp in patch 043 prevents the abort regardless of which producer created the size mismatch.
What alternative solutions did you explore? (Optional)
If the retest on 9.4.90-2+ still crashes, the next step is to strip the BOM before the file is read back for upload, using the existing removeInvisibleCharacters helper, and to reconcile the JS blob size with what native actually stored rather than trusting the JS-computed byte count. I would not pursue either until the retest proves the shipped clamp is insufficient.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/100367) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Yes, this is the fix. The crash log shows an iOS native SIGABRT in -[RCTBlobManager resolve:offset:size:]. Patch 043 now clamps the range instead of throwing (patch:18). The patch is still on main.
No. The attached file is a CSV, and the multi-level tag import uploads the raw file.
Other recent tag and spreadsheet PRs, such as category import mapping and the PolicyUtils refactor, don't touch the multi-level tag upload or the iOS blob code.
Not verified: I didn't test on an iOS device. This crash is iOS-native only, so a web test can't confirm it. Your failed repro on a current build is the best evidence that the fix works.
Activity
applause-bot commentedon Sep 4, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/02ef022d669ebf7eb65e69a273cb6637aa3c3ca73139f21d8e1cb8a209276c1d
https://platform.applause.com/services/links/v1/external/00f88d2f20c3ac0dcbdc79c48c559406cc200ca98262d71d0001b4fbc68b4765
https://platform.applause.com/services/links/v1/external/9a75ab88b74542cdbac9aaae86d98c53777e26250ecea2fc73d3287b250cfc91
MelvinBot commentedon Sep 24, 2026
Proposal
TL;DR: This is not a tags bug. It is the known iOS native blob crash [Due for payment 2026-09-28] [Sentry: APP-KDN] iOS HybridApp native crash — RCTBlobManager slices a blob past its data length, already fixed by Fix APP-KDN: preserve NUL bytes in iOS TurboModule strings and clamp blob ranges, deployed to production in 9.4.90-2 on 2026-09-21. This report is from 9.4.71-0. Please retest on 9.4.90-2 or later — I expect it no longer crashes.
What is the root cause of that problem?
The app aborts on an uncaught Objective-C
NSRangeExceptionfrom-[NSData subdataWithRange:]inside RCTBlobManager, on thecom.meta.react.turbomodulemanager.queuebackground queue. Because the method isvoid/async, React Native re-throws the exception out of the dispatch block, so no JS frame can catch it and the process callsabort().Multi-level tags is the only spreadsheet importer that re-uploads the raw file instead of posting parsed JSON, so it is the only import flow that builds a blob:
src/libs/actions/Policy/Tag.ts:902callsreadFileAsync(spreadsheet.fileURI, …).src/libs/fileDownload/FileUtils.ts:294doesnew File([blob], …), which wraps one blob as a part of another. That is the only call that reachesresolve:offset:size:→subdataWithRange:.The slice overruns because the JS-side blob descriptor records more bytes than native actually stored. JS counts the string's full UTF-8 byte length, while the
NSStringbuilt from the JSI string is shorter — sosubdataWithRange:{0, size}reads past the end.Why this particular file triggers it: the attached CSV is 327 bytes, contains zero NUL bytes, and starts with two back-to-back UTF-8 BOMs (plus a third inside the quoted
"Department GL"header):Foundation's UTF-8 decoder drops a leading BOM, giving native 324 bytes against a JS descriptor of 327 — a 3-byte shortfall, matching the Sentry message shape (
range {0, 321} exceeds data length 318). This matters for triage: the earlier APP-KDN work attributed the desync to embedded NUL bytes, which this file does not have. The BOM is a second, distinct producer of the same desync.What changes do you think we should make in order to solve the problem?
No new App code change is needed for the crash. Patch
react-native+0.86.0+043+clamp-blob-range-APP-KDN.patchclamps the range instead of raising, so it covers the BOM case as well as the NUL case:Recommended next steps:
U+FEFFprefix. That would be a separate, cosmetic follow-up.Supporting evidence
Crash report (
Bug7250654_…Expensify-2026-09-04-024934.txt):EXC_CRASH/SIGABRT,abort() called, faulting queuecom.meta.react.turbomodulemanager.queue.lastExceptionBacktrace, innermost first, with image indexes resolved (12 =React, 18 =CoreFoundation, 24 =Foundation, 31 =libobjc):Two independent checks support the RCTBlobManager mapping:
Reactoffsets sort in the same order as the three methods appear inRCTBlobManager.mm(resolve:at :101 <resolve:offset:size:at :108 <createFromParts:withId:at :199/:213).The
-[NSConcreteData copyWithZone:]+0symbol is a mis-symbolication: it is an adjacentNSDataconcrete-cluster method in Foundation's__TEXT, andsymbolLocation: 0indicates the symbolicator used a mismatched Foundation build for iOS 16.7 (20H19).Byte verification of the attached CSV: 327 bytes total; stripping NUL bytes leaves 327 bytes, so there are none. Three
EF BB BFsequences, two at offset 0.Timeline: crash captured 2026-09-04 on 9.4.71-0 → PR #101062 merged 2026-09-18 → deployed to production in 9.4.90-2 on 2026-09-21. The reported build predates the fix.
Alternatives considered and ruled out:
SIGABRTwith no JS frames, and the App-side spreadsheet code (ImportSpreadsheet.ts,ImportSpreadsheetColumns.tsx) uses.at()with?? ''fallbacks throughout, with no non-null assertions.React.framework(index 12). Thenitro-thread-4thread sitting insqlite3_stepis unrelated background Onyx I/O.NSInvalidArgumentExceptionfromNSJSONSerializationor base64 decoding. Ruled out by the symbolicated APP-KDN stack: the raiser issubdataWithRange:with a range-exceeds-length message.ios/Podfile:95,RCT_NEW_ARCH_ENABLED=1) and every ObjC TurboModule is invoked viaNSInvocation, so that frame pattern is expected forRCTBlobManager, which is a real TurboModule.Not verified: I did not reproduce on an iOS device. The BOM mechanism specifically — that Foundation drops a leading BOM during the JSI-string-to-
NSStringconversion — is inferred from the byte counts and has not been confirmed on device. The crash identification itself does not depend on it: the clamp in patch 043 prevents the abort regardless of which producer created the size mismatch.What alternative solutions did you explore? (Optional)
If the retest on 9.4.90-2+ still crashes, the next step is to strip the BOM before the file is read back for upload, using the existing
removeInvisibleCharactershelper, and to reconcile the JS blob size with what native actually stored rather than trusting the JS-computed byte count. I would not pursue either until the retest proves the shipped clamp is insufficient.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/100367)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Sep 24, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @eh2077 (
External)melvin-bot commentedon Sep 24, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
melvin-bot commentedon Sep 29, 2026
@eh2077 Huh... This is 4 days overdue. Who can take care of this?
eh2077 commentedon Oct 1, 2026
checking
melvin-bot commentedon Oct 2, 2026
@eh2077 this issue is now 4 weeks old, please consider:
Thanks!
melvin-bot commentedon Oct 6, 2026
@eh2077 Huh... This is 4 days overdue. Who can take care of this?
eh2077 commentedon Oct 8, 2026
I can't reproduce the issue. @MelvinBot please help check again on recent fixes to see if they're relevant issues.
MelvinBot commentedon Oct 8, 2026
Your result matches the fix. The crash was already fixed by Fix APP-KDN: preserve NUL bytes in iOS TurboModule strings and clamp blob ranges. It reached production in 9.4.90-2. The report came from 9.4.70-0, before that fix.
I recommend you close this issue as a duplicate of [Due for payment 2026-09-28] [Sentry: APP-KDN] iOS HybridApp native crash — RCTBlobManager slices a blob past its data length, which is already closed.
Recent fixes I checked
SIGABRTin-[RCTBlobManager resolve:offset:size:]. Patch 043 now clamps the range instead of throwing (patch:18). The patch is still onmain.Other recent tag and spreadsheet PRs, such as category import mapping and the
PolicyUtilsrefactor, don't touch the multi-level tag upload or the iOS blob code.Not verified: I didn't test on an iOS device. This crash is iOS-native only, so a web test can't confirm it. Your failed repro on a current build is the best evidence that the fix works.
view run
melvin-bot commentedon Oct 13, 2026
@eh2077 Huh... This is 4 days overdue. Who can take care of this?
eh2077 commentedon Oct 14, 2026
I can't reproduce
Screen.Recording.2026-10-14.at.10.42.21.PM.mov