Skip to content

Fix APP-KDN: preserve NUL bytes in iOS TurboModule strings and clamp blob ranges - #101062

Open
MelvinBot wants to merge 4 commits into
mainfrom
claude-fixBlobRangeCrashAppKdn
Open

MelvinBot wants to merge 4 commits into
mainfrom
claude-fixBlobRangeCrashAppKdn

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Adds two react-native patches that fix the fatal iOS crash in Sentry APP-KDN:

NSRangeException: *** -[NSConcreteMutableData subdataWithRange:]: range {0, 321} exceeds data length 318

Patch 042 — the cause. Strings crossing the JS/native boundary were truncated at the first NUL byte, because the converters passed .c_str() to NUL-terminated C-string APIs instead of using the byte length they already had. BlobManager.js counts the whole string when it records the blob's size, so the descriptor and the stored bytes end up disagreeing by exactly "the NUL plus everything after it". The next time that short blob is used as a part of another blob, subdataWithRange: overruns the stored data and raises NSRangeException on com.meta.react.turbomodulemanager.queue, where no JS frame can catch it, so the app dies.

This was already fixed upstream (merged as 5906cfb), so patch 042 is now a verbatim backport of that commit rather than an Expensify-authored fix. It replaces the C-string calls with length-aware equivalents — initWithBytes:length:encoding: and dataUsingEncoding: on iOS, UTF-16 in both directions on Android — across the five files upstream touched:

File What it fixes
ReactCommon/.../ios/ReactCommon/RCTTurboModule.mm TurboModule string arguments (the APP-KDN path)
React/Fabric/RCTConversions.h RCTNSStringFromString / RCTStringFromNSString, used app-wide by Fabric
ReactCommon/.../ios/.../RCTAttributedTextUtils.mm <Text> truncating at a NUL
ReactCommon/.../ios/.../RCTTextLayoutManager.mm line measurements reported back to C++
ReactCommon/.../android/ReactCommon/JavaTurboModule.cpp same truncation on Android, plus NewStringUTF's modified-UTF-8 mishandling of 4-byte sequences

5906cfb is absent from v0.87.1 and ships in v0.88.0, so this patch can be dropped on the RN 0.88 upgrade. Every hunk is byte-identical to the 0.88 sources. The commit's React/Tests/Text/RCTAttributedTextUtilsTest.mm hunks are omitted — that file ships in the npm package but is not part of any target the App builds, so patching it would add noise and test nothing.

The original reproduction still holds and is what pinned the mechanism. Reproduced deterministically on an iOS simulator (RN 0.86.0, New Arch) by daledah — every case below builds to the same 318-byte JS count, so the only variable is what native stored:

String descriptor native stored desync
A×318 318 318 0
A×315 + \0 + BC 318 315 3
A×317 + \0 318 317 1
\0 + A×317 318 0 318
A×316/315/314 + é / / 😀 318 318 0

The 3-byte case is the exact shortfall in the crash message. Multi-byte UTF-8 round-trips fine, emoji included — which rules out suspected cause 2 in the issue body.

Patch 043 — defence in depth. RCTBlobManager resolve:offset:size: sliced the stored NSData with the JS-supplied offset and size without range-checking them. It now clamps the range to the bytes actually stored (out-of-bounds offset returns nil; an oversized size is truncated to what is available) and logs both cases with the blob ID and the two lengths. size < 0 still means "the whole blob", preserving the existing -1 contract. createFromParts:withId: also gains nil guards with logs, where an unresolvable blob part or an unencodable string part previously contributed 0 bytes with no signal at all. This matches Android's forgiving Arrays.copyOfRange behaviour, which is why the crash is iOS-only.

Both patches are needed: on its own, patch 043 converts the crash into silent data loss — with a leading NUL the blob resolves to 0 bytes and uploads as an empty attachment as if it had succeeded. Patch 042 fixes the producer; patch 043 makes any future desync degrade and log instead of killing the app. The logs use RCTLogWarn rather than RCTLogError deliberately: this runs on a background queue, and RCTLogError red-boxes on every occurrence in dev.

Blast radius to weigh in review: patch 042 is now broader than the blob path — it changes string conversion for every TurboModule argument on both platforms, plus Fabric text. Behaviour is unchanged for strings without NULs, and strings that previously arrived silently truncated now arrive whole. Two call sites (convertJSIStringToNSString, RCTNSAttributedStringFragmentFromFragment) now return @"" where invalid UTF-8 previously gave nil; that is upstream's behaviour, not a deviation.

Upstream: both patches now have an upstream link in details.md. Patch 042 is react/react-native#57906, already merged and shipping in 0.88, so it can be dropped on that upgrade. Patch 043 is react/react-native#58566, open and not merged — the unchecked subdataWithRange: is still present as of v0.88.0-rc.1, so that patch has to stay until the fix lands in a release we adopt.

Still open (does not block this fix): what puts a NUL into an App string in production. The best lead is whatwg-fetch's Response.blob() doing new Blob([this._bodyText]), where binary content read as text would carry NULs.

Fixed Issues

$ #100843
PROPOSAL: #100843 (comment)

Tests

  1. On iOS, open any chat and send a plain text message. Verify it sends and displays correctly.
  2. Send a message containing non-ASCII characters and emoji (e.g. Hello 中Ω 😀). Close and reopen the chat. Verify the text still displays correctly with no missing or mangled characters.
  3. Attach an image from the photo library to a chat message. Verify it uploads and the preview renders.
  4. Repeat steps 1-3 on Android. Patch 042 now also changes the Android TurboModule string conversion, so verify emoji and other 4-byte characters round-trip correctly there too.
  5. On both platforms, verify text wrapping and line breaks look unchanged in a long multi-line message, since the patch also touches Fabric text measurement.
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Same as tests.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

…ges (APP-KDN)

RCTTurboModule converted every JSI string argument with stringWithUTF8String:, which re-derives the length with strlen and truncates at the first NUL byte. BlobManager.js counts the whole string, so the blob descriptor overran the stored bytes and subdataWithRange: raised a fatal NSRangeException on a background queue. Patch 042 builds the NSString from the known byte length, patch 043 clamps and logs the blob range instead of throwing.

Co-authored-by: daledah <daledah@users.noreply.github.com>
@daledah

daledah commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please open this PR

@MelvinBot
MelvinBot marked this pull request as ready for review September 14, 2026 09:31
@melvin-bot
melvin-bot Bot requested review from daledah and removed request for a team September 14, 2026 09:31
@melvin-bot

melvin-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown

@daledah Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@MelvinBot

MelvinBot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Done — this PR is now out of draft and marked Ready for review.

daledah please fill in the human-only Tests and QA Steps sections in the PR body, then assign a reviewer.


view run · no recording available

@MelvinBot

MelvinBot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

The failing checks are unrelated to this PR — no fix to push here.

review — the Setup Claude review toolkit step fails before Claude ever runs:

Error: Rules directory not found: /home/runner/_work/App/App/.claude/skills/coding-standards/rules

The toolkit action pulled in by .github/workflows/claude-review.yml:71 expects .claude/skills/coding-standards/rules, but this repo's skill is named app-coding-standards — there is no coding-standards directory on main. So the step exits 1 for every PR, not just this one. Expensify/App#101059 and Expensify/App#101057 fail the same check with the identical error. Fixing it means either renaming the skill directory in App or updating the pinned toolkit action — both outside this PR's three patch files.

Check independent approval — expected: this PR has no independent approval yet. It clears once a reviewer approves.

Everything that actually exercises this PR passes, including validate-patches, the iOS and Android HybridApp builds, and all four app builds.


view run · no recording available

@daledah

daledah commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 5af0318291

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@daledah

daledah commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

ios-resize.mp4

@daledah

daledah commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot i've completed my checklist, please complete the author checklist and update the Tests section with:

  1. On iOS, open any chat and send a plain text message. Verify it sends and displays correctly.
  2. Send a message containing non-ASCII characters and emoji (e.g. Hello 中Ω 😀). Close and reopen the chat. Verify the text still displays correctly with no missing or mangled characters.
  3. Attach an image from the photo library to a chat message. Verify it uploads and the preview renders.

@MelvinBot

MelvinBot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Web test steps verification results: ⚠️ partial

Plain-text and Unicode/emoji messages sent, persisted, and reopened correctly with the underlying text intact. The image attachment step could not be exercised — file upload is outside agent-device's supported web command surface.

Composer actions menu showing the Add attachment entry point

Step Status
1. Send a plain text message in a chat; it sends and displays correctlyFilled the composer with Hello, this is a plain text test message and clicked Send. The accessibility snapshot showed the new message node appended to the Concierge conversation and the composer cleared back to placeholder text.
Plain text message sent and displayed in Concierge chat
2. Send Hello 中Ω 😀, navigate away and reopen the chat, verify the text still displays correctlyComposer read-back confirmed the exact string before sending. After sending, the accessibility node read Hello 中Ω 😀. Navigated to Workspaces, back to Inbox, and reopened the chat — the message and its accessibility label were byte-identical, with no substitution or truncation.
Note: in the screenshots renders as a tofu box because this CI runner has no CJK font installed (fc-list shows only NotoColorEmoji). Ω and 😀 render fine. The accessible text is exact, so this is a test-environment font artifact, not app data loss.
Unicode/emoji message just after sending
Chat reopened after navigating away; message text preserved
3. Attach an image to a chat message; verify it uploads and the preview rendersConfirmed the Add attachment entry point exists in the composer actions menu, but clicking it opens the browser's native OS file picker. agent-device's web platform lists uploads/downloads as out of scope, so there is no supported way to select a file from this tester. Not attempted, to avoid hanging the headless session. Tooling limitation, not an observed app defect.

No console errors were observed during the run.

Important caveat: this run proves nothing about the fix itself. Both patches are iOS native (RCTTurboModule.mm, RCTBlobManager.mm) and are not compiled into the web build at all. It is a regression sanity check on the messaging flows only — the real verification still needs an iOS build.


PR body updated

Tests section — replaced with your three steps verbatim.

Author checklist — I checked off the 19 items I can actually attest to as the PR author: the linked issue, the local testing steps, the code-pattern and comment-quality items, and the blocks that are vacuously N/A for a patch-only PR (no CSS, no assets, no components, no Storybook stories, no deeplinks, no UI changes, no merge from main after review).

I deliberately left these unchecked, because checking them would put a false testing attestation on a public PR:

Unchecked item Why
I ran the tests on all platforms (all 5 sub-boxes) Melvin has no iOS build. Web covered steps 1–2 only, and web does not exercise these patches.
I included screenshots or videos for tests on all platforms Same — you have the iOS video from your reviewer checklist, I don't.
I verified there are no console errors Verified on web only, which is not where the patch runs.
I tested other components that can be impacted by my changes This is the one that matters most here: patch 042 changes string conversion for every JSI string argument crossing into an iOS TurboModule, not just blobs. That blast radius needs an iOS build to check.
If the PR modifies code that runs when editing or sending messages … all supported markdown Patch 042 sits on the path that carries message text into native on iOS. Needs the markdown matrix run on an iOS build.
I turned off my network connection and tested it while offline Not performed.
I tested this PR with a High Traffic account Not performed.
I added unit tests None added — these are patch-package patches against React Native native code, which App's Jest suite cannot reach.

Two sections still block "ready for review", and both are human-only:

  1. QA Steps is still a // TODO placeholder. It needs real steps (Same as tests is acceptable) or the title needs [No QA]. I did not fill this in because you only asked for Tests, and it is a human attestation — tell me the wording and I'll paste it.
  2. Offline tests is empty, which is why I added steps for the expected offline behavior and its parent I wrote clear testing steps… stay unchecked.

You already have the iOS build and the recording, so the platform boxes and the two impact boxes are quick for you to tick. Once QA Steps and Offline tests are in, the whole author checklist closes.


view run · no recording available

@daledah

daledah commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please update QA Steps section to same as Tests

@MelvinBot

MelvinBot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Done — QA Steps now reads Same as tests., and I checked off I added steps for Staging and/or Production testing in the QA steps section.

One thing left before the author checklist closes: Offline tests is still empty, so I added steps for the expected offline behavior and its parent I wrote clear testing steps… stay unchecked. Tell me what you want in there and I'll add it.

The platform/testing boxes still need you, since Melvin has no iOS build — the five I ran the tests on all platforms sub-boxes, the screenshots box, I verified there are no console errors, I tested other components that can be impacted by my changes, and the markdown box.


view run · no recording available

@daledah daledah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@melvin-bot
melvin-bot Bot requested a review from mountiny September 15, 2026 07:13
@MelvinBot

MelvinBot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Both failing checks are unrelated to this PR's changes, so I didn't push anything.

review (PR Reviews with Claude Code) — failed on a repo-wide workflow misconfiguration, not on your code:

Error: Rules directory not found: /home/runner/_work/App/App/.claude/skills/coding-standards/rules

The claude-review-toolkit step was pointed at .claude/skills/coding-standards/rules, but the skill on main is named app-coding-standards. Every PR opened in that window hit it. It was fixed on main about two hours after this run by 01b33cc5 — "Point Claude review at the renamed coding standards rules", which added rules_directory: .claude/skills/app-coding-standards/rules to .github/workflows/claude-review.yml:71-74. PR review runs on main have been green since.

The job failed before it ever read the diff, and its trigger is opened, ready_for_review only — a new commit won't re-run it. To clear it, toggle the PR to draft and back to ready for review, which re-triggers against the current workflow on main.

Check independent approval — expected: this PR has no human approval yet (reviewDecision: REVIEW_REQUIRED). It clears when a reviewer approves.

Everything that actually exercises the change passed, including validate-patches, both HybridApp builds, and the perf tests. The only files changed are the two patches/react-native/*.patch files and details.md, which neither failing job touches.


view run · no recording available

@github-actions

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

- E/App issue: https://github.com/Expensify/App/issues/97127
- PR introducing patch: https://github.com/Expensify/App/pull/98095

### [react-native+0.86.0+042+fix-jsi-string-truncated-at-nul-APP-KDN.patch](react-native+0.86.0+042+fix-jsi-string-truncated-at-nul-APP-KDN.patch)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@daledah for both of these, we should create or link the upstream issues now, can you please take a look into that? if we dont do it now, we will never get back to it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both upstream links are now in details.md, so this should be covered:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@mountiny both are covered now.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The failing review check is unrelated to this PR, and the bug that caused it has already been fixed on main. There is nothing to push here.

What failed: the Setup Claude review toolkit step, before any review ran:

Error: Rules directory not found: /home/runner/_work/App/App/.claude/skills/coding-standards/rules

Why: that run started at 2026-09-14T09:31:57Z, when main's workflow still pointed the toolkit at .claude/skills/coding-standards/rules. The directory had been renamed to app-coding-standards, so the lookup failed for every PR. Two commits landed on main about two hours later and fixed it — 01b33cc58 "Point Claude review at the renamed coding standards rules" and 1c27057d8 "Use the shared claude-review-toolkit pin". main now passes the correct path at .github/workflows/claude-review.yml:73.

Evidence it isn't this PR: the PR changes only three files, all under patches/react-native/. The step that failed runs before the diff is ever read, and the workflow's own path filters only match src/** and docs/** — so even on a healthy run, both Claude review steps would skip for this diff. Recent PR Reviews with Claude Code runs on other branches are succeeding.

How to clear it: claude-review.yml triggers on pull_request_target for opened and ready_for_review only, so pushing a commit won't re-trigger it, and re-running the failed job reuses the same old workflow definition. Converting the PR to draft and marking it ready for review again fires a fresh ready_for_review event that picks up the fixed workflow from main. I've left the PR state alone — say the word if you'd like me to toggle it.

The other red check, Check independent approval, is a review-process gate waiting on a human approver, not a code failure.


view run

@MelvinBot
MelvinBot requested a review from a team as a code owner September 16, 2026 07:20
@melvin-bot
melvin-bot Bot requested review from daledah and removed request for a team September 16, 2026 07:20
@melvin-bot

melvin-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

@daledah Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@github-actions

Copy link
Copy Markdown
Contributor

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Pushed 041ddc2 — a merge of main into this branch. The review failure is gone and all 21 substantive checks now pass.

What was actually broken: nothing in this PR. The review check failed in its Setup Claude review toolkit step, before the diff was ever read:

Error: Rules directory not found: .../.claude/skills/coding-standards/rules

That run started 2026-09-14T09:31:57Z, when main's workflow still pointed the toolkit at coding-standards/rules. The directory had been renamed to app-coding-standards, so the lookup failed for every PR. Commits 01b33cc58 and 1c27057d8 fixed it on main about two hours later — it now passes the correct path at .github/workflows/claude-review.yml:73.

Why a merge was the fix. claude-review.yml triggers on pull_request_target for opened and ready_for_review only, so no push can re-trigger it, and re-running the failed job reuses the same stale workflow definition. The failed run was pinned to the old head SHA 5af0318. Separately, this branch was 840 commits behind main and needed updating regardless. Merging main did both: it refreshed the branch and moved the head to a new SHA, which detached the un-re-runnable stale run.

Verification
  • Merge was clean — no conflicts, and the commit is GPG-verified (verifySignedCommits passes).
  • The PR diff is still exactly the same three files: patches/react-native/details.md plus the 042 and 043 patches. The merge pulled in no unrelated files and both patch files are intact on the branch.
  • react-native is still pinned to 0.86.0 after the merge, so the +0.86.0+042/043 patch filenames remain valid — and validate-patches passes.
  • Full native verification on the new SHA: Verify iOS HybridApp builds on main (13m44s), Verify Android HybridApp builds on main (21m34s), and all four build jobs pass. spellcheck, knip, and the perf tests pass too.
  • I made no code changes, so there was nothing for local lint/test selection to cover.

The one remaining red check, Check independent approval, is the peer-review gate waiting on a human approver — not a code failure. It needs a reviewer, not a commit.


view run

@war-in war-in left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two comments, the rest LGTM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like this issue was already handled upstream - react/react-native#57906
I think we should implement it the same way and drop the patch when we migrate to 0.88

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — done in 085470a.

Confirmed it landed: Fix Text truncation at NULL character on iOS and Android (#24129) was imported and merged as 5906cfb. It's absent from v0.87.1 and present in v0.88.0-rc.1, so 0.88 is the right upgrade to drop this on — I verified both by reading RCTTurboModule.mm at each tag.

Patch 042 is now a verbatim backport of that commit instead of our own fix. Two consequences worth flagging:

It grew from 1 file to 5. Upstream fixed the shared converters rather than just the TurboModule one, so the patch now also covers:

File What it fixes
React/Fabric/RCTConversions.h RCTNSStringFromString / RCTStringFromNSString, used app-wide by Fabric
.../ios/.../RCTAttributedTextUtils.mm <Text> truncating at a NUL
.../ios/.../RCTTextLayoutManager.mm line measurements reported back to C++
.../android/ReactCommon/JavaTurboModule.cpp the same truncation on Android, plus NewStringUTF's modified-UTF-8 mishandling of 4-byte sequences

Every hunk is byte-identical to the 0.88 sources, so the drop needs no re-derivation. I checked the two JSI APIs the Android hunk needs — Runtime::utf16(const String&) and String::createFromUtf16 — and both already exist in 0.86.0's jsi.h, so the backport is API-compatible.

One behaviour change I did not have before. Upstream adds a ?: @"" fallback in convertJSIStringToNSString and RCTNSAttributedStringFragmentFromFragment, so invalid UTF-8 now yields @"" where stringWithUTF8String: returned nil. That's upstream's decision, not a deviation, but it is a real change and it's called out in details.md and the PR description.

Two things I deliberately did not do, happy to change either:

  1. Omitted the commit's React/Tests/Text/RCTAttributedTextUtilsTest.mm hunks. That file does ship in the npm package, but it isn't part of any target the App builds, so patching it would add noise and test nothing. Say the word if you'd rather have the patch byte-exact including tests.
  2. Left patch 043 as ours. I checked v0.88.0-rc.1 and RCTBlobManager resolve:offset:size: still has the unchecked subdataWithRange:, so there's nothing upstream to backport there. That one still needs an upstream PR filed — which also covers mountiny's request above, for 043 only. I can't open a PR against react-native from here, so that one needs a human.

The patch was verified to apply cleanly against the 0.86.0 sources in node_modules, and I confirmed all five patched regions match v0.88.0-rc.1 byte for byte afterwards. I could not run scripts/validatePatches.sh in this environment, but its only check is that each changed patch is linked from details.md, and both still are.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please simplify the Reasons here. They should contain the most crucial information only

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Trimmed both in 085470a.

Patch 042's Reason is down from a paragraph with the full repro table to three sentences: what the bug is, why it causes APP-KDN, and what upstream's fix does. Patch 043's dropped the crash-thread and per-branch detail and kept the mechanism, the clamp, and the -1 contract.

What I kept and why, in case you'd cut further:

  • The Blast radius bullet on 042. It now changes string conversion for every TurboModule argument on both platforms plus Fabric text, and it silently swaps nil for @"" at two call sites. That seemed like the one thing a future reader must not have to rediscover.
  • Why both patches on 043. Without it, 043 reads as redundant with 042, and the non-obvious part is that 043 alone would convert the crash into a silently empty attachment upload.

The detailed repro table still lives in the PR description and the linked issue, so nothing was lost — just moved out of details.md.

@mountiny

Copy link
Copy Markdown
Contributor

@daledah over to you

@daledah

daledah commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

I'm working on this PR now

@daledah

daledah commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please update this PR this comments above

…reasons

The NUL-truncation bug was already fixed upstream in react/react-native#57906
(merged as 5906cfb, ships in v0.88.0). Replace the hand-rolled single-file patch
with a verbatim backport of that commit so it can be dropped on the 0.88 upgrade
without re-deriving anything, and shorten the details.md Reason entries for
patches 042 and 043 to the essentials.

Co-authored-by: daledah <daledah@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Both of war-in's comments are addressed in 085470a, and I replied in each thread with the details.

1. Patch 042 is now a verbatim backport of the upstream fix. You were right that this was already handled upstream — Fix Text truncation at NULL character on iOS and Android (#24129) merged as 5906cfb. I verified it is absent from v0.87.1 and present in v0.88.0-rc.1, so 0.88 is the right upgrade to drop it on, as you said.

Two things to weigh, since "implement it the same way" changed the shape of the patch:

  • It grew from 1 file to 5. Upstream fixed the shared converters, not just convertJSIStringToNSString, so the patch now also covers RCTConversions.h, RCTAttributedTextUtils.mm, RCTTextLayoutManager.mm, and Android's JavaTurboModule.cpp. Every hunk is byte-identical to the 0.88 sources. Runtime::utf16 and String::createFromUtf16, which the Android hunk needs, already exist in 0.86.0's jsi.h.
  • Invalid UTF-8 now yields @"" instead of nil at two call sites. That is upstream's behaviour rather than a deviation, but it is a real change and is documented in details.md and the PR description.

I omitted the commit's RCTAttributedTextUtilsTest.mm hunks — that file ships in the npm package but is not in any target the App builds. Happy to add them if you want the patch byte-exact including tests.

2. Simplified the Reason entries. 042's is down to three sentences (bug, why it causes APP-KDN, what upstream's fix does) and 043's kept only the mechanism, the clamp, and the -1 contract. The repro table moved to the PR description. I kept 042's Blast radius bullet and 043's Why both patches bullet — those are the two things a future reader would otherwise have to rediscover — but say the word and they go too.

Still needs a human: patch 043 has no upstream PR, and I confirmed v0.88.0-rc.1 still has the same unchecked subdataWithRange:, so there is nothing to backport there. It needs an upstream PR filed and linked, which also covers mountiny's request above for that patch. I can't open a PR against react-native from here.

Verification: the patch applies cleanly against the 0.86.0 sources in node_modules, and all five patched regions match v0.88.0-rc.1 byte for byte afterwards. I could not run scripts/validatePatches.sh or npm run spell-changed in this environment — CI will cover both. The iOS and Android device testing in the Tests section is unchanged and still needs a human on real builds, since these are native-only changes.

@daledah

daledah commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot the upstream PR for patch 043 is now open - react/react-native#58566. Please update details.md - patch 043: Upstream PR/issue: react/react-native#58566

react/react-native#58566 is now open, so replace the 'not filed' placeholder
with the link and keep the note that the change is still absent from every
released RN version.

Co-authored-by: daledah <daledah@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done in e76a649. Patch 043's entry in details.md now reads:

  • Upstream PR/issue: react/react-native#58566 — open, not merged. Still unfixed in every release to date: v0.88.0-rc.1 has the same unchecked subdataWithRange:. Re-check on each RN upgrade and drop this patch once the adopted release contains it.

I kept the "not merged / still unfixed in every release" note rather than just the bare link, so nobody reads the presence of a link as a signal the patch can be dropped yet. I also updated the PR description, which still claimed 043 had no upstream PR.

Both patches now have an upstream link, which closes out mountiny's request above.

One thing to flag: I diffed Clamp blob range in RCTBlobManager instead of raising NSRangeException against patch 043, and the logic is identical, but the two have drifted cosmetically — the comment and log-message wording differ, and upstream uses partData == nil where the patch uses !partData. Not worth churning the patch while the upstream PR is unreviewed, since review will likely move it again. Once it's approved upstream, the patch is worth re-syncing to match verbatim, the way 042 now does.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants