Skip to content

fix(mobile): prevent Hermes startup crash from Intl.Segmenter - #324

Merged
dbalders merged 3 commits into
mainfrom
fix/mobile-segmenter-crash
Oct 2, 2026
Merged

dbalders merged 3 commits into
mainfrom
fix/mobile-segmenter-crash

Conversation

@dbalders

@dbalders dbalders commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Harness Preview 1.2.0 (4) crashed before opening because shared monogram validation constructed Intl.Segmenter during module initialization, which is unavailable in the mobile Hermes runtime. Start from upstream #11984, then use the same Unicode grapheme counter on every runtime to preserve saved international monograms and prevent joiners from bypassing the two-grapheme limit.

Why

Restore launch on TestFlight and locally signed iPhone builds while preserving the shared validation behavior.

Validation

  • vp check --fix: 0 errors; 781 existing warnings.
  • vp run typecheck.
  • vp test: 17,364 passed; 56 skipped. Focused contracts tests: 67 passed.
  • The corrected revision launched in a signed Release/Hermes build on iOS 26.5 Simulator. The earlier upstream-only fix launched on the connected iPhone running iOS 26.6.2 as Preview 1.2.0 (5); the user confirmed it opened.
  • Cross-model review identified a joiner bypass and rejected saved international monograms. Both reproduced in failing tests and passed after the correction. Tests import the contracts with Intl.Segmenter unavailable.
  • GitHub CI passed at 3445a3b6fd; current-head cross-model and TritonAI reviews found no actionable findings. CodeRabbit was rate limited and covers the earlier revision.
  • Inspected the installed dependency's grapheme entry and import graph: JavaScript and Unicode tables, with no WebAssembly imports. The corrected Hermes launch and CI desktop/CLI build passed, resolving the review's unverified WebAssembly compatibility concern.

Risk and Recovery

The replacement avoids the unavailable runtime API and pins unicode-segmenter 0.17.3 with no transitive dependencies. Regression coverage exercises accepted and rejected inputs, including Indic conjuncts, decomposed Hangul, Thai/Lao marks, and joiner-separated letters. Reverting would restore the mobile startup crash.

Release and Deployment Impact

A new mobile bundle is required to replace the crashing TestFlight version. This PR does not upload a build or trigger a nightly.

Upstream Alignment

The first commit is the exact patch from upstream commit 6f7aaffe26f0e6ae3d47b0ae512b322f890e0f43. Subsequent downstream commits add regression coverage and replace its approximate counter to address verified review findings while keeping validation consistent across runtimes.

Checklist

  • The PR is not a draft and local validation is complete.
  • The change is focused and avoids unrelated churn.
  • New behavior has regression coverage.
  • No secrets, credentials, private URLs, or sensitive logs are included.
  • Verified review findings are addressed and current-head GitHub CI passed.

Implementation: GPT-6.1 Sol via Codex

@github-actions github-actions Bot added the size:S 10-29 effective changed lines (test files excluded in mixed PRs). label Oct 2, 2026
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: dbalders/TritonAI-Harness/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 47ddc92e-9321-4773-84a6-d3fd2d35944b

📥 Commits

Reviewing files that changed from the base of the PR and between efc6769 and 21557ab.

📒 Files selected for processing (2)
  • packages/contracts/src/orchestration.mobile-runtime.test.ts
  • packages/contracts/src/orchestration.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

ProjectMonogramText no longer relies on Intl.Segmenter to enforce its two-cluster limit. A mobile-runtime test disables Intl.Segmenter and checks monogram decoding outcomes.

Changes

Monogram validation

Layer / File(s) Summary
Cluster counting and mobile-runtime validation
packages/contracts/src/orchestration.ts, packages/contracts/src/orchestration.mobile-runtime.test.ts
ProjectMonogramText uses a grapheme-counting approximation for its two-cluster limit. The test checks accepted and rejected ProjectIconOverride monograms when Intl.Segmenter is unavailable.

Priority: ⬆️ High

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 21557

The startup fix is mergeable. Some uncommon monograms may still be accepted or rejected incorrectly.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 21557

The change removes the mobile startup dependency without adding privileges or new access paths. The main remaining risk is Unicode validation compatibility across clients, rather than a demonstrated security vulnerability.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure remains project display metadata carried through shared contracts, server persistence, and web rendering. The traced path does not show new tenant, credential, service, or infrastructure authority arising from changed monogram acceptance.

Trust Boundaries and Controls

  • inferred — Broader acceptance of some Unicode sequences does not establish an authorization bypass: the traced value remains icon text, with unchanged character and length controls and escaped rendering. Authorization middleware was outside this inspection, so this conclusion is limited to the traced monogram path.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the mobile Hermes startup crash and its prevention. It matches the primary change.
Description check ✅ Passed The description covers the required Summary, Why, Validation, Risk and Recovery, Release and Deployment Impact, Upstream Alignment, and Checklist sections. UI Changes is omitted, which is appropriate …
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dbalders

dbalders commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner Author

Cross-model review

Implementation (PR footer): GPT-6.1 Sol via Codex
Reviewer: Claude claude-opus-5-5 (high) via autoreview, P2 and above
Reviewed commit: 3445a3b6fd04a090b5bcb8ae756b80ab6c89fdba
Result: scoped-clean

autoreview scoped-clean: no accepted/actionable findings in the selected Git scope and priority
overall: patch is correct (0.8)
The change replaces the Intl.Segmenter instance created at module load with countGraphemes from the pure-JS unicode-segmenter package. Hermes (the JS engine used by React Native on mobile) has no Intl.Segmenter, so the old code crashed the mobile app on launch; this fixes that. I confirmed from the package's published docs and type definitions that unicode-segmenter/grapheme exports countGraphemes, which returns the number of extended grapheme clusters.

Using one implementation on every runtime keeps validation consistent between server and mobile. The two-grapheme limit and the existing character pattern are otherwise unchanged. The new tests remove Intl.Segmenter and check three things: zero-width joiner bypasses are rejected, and Devanagari conjuncts, Hangul text decomposed into separate jamo (NFD), and Thai/Lao clusters with a spacing mark are accepted.

Some expectations depend on the library following Unicode 15.1+ segmentation rules (for example the Indic conjunct rule GB9c, which makes "क्ष" one grapheme). Recent unicode-segmenter releases claim support for these rules, but I could not run the tests to confirm the exact counts.

The dependency is pinned to an exact version rather than using the workspace catalog. That is a style choice, not a defect. I found no actionable P0–P2 defects.


Automated cross-model review on ChelsClaw, updated 2026-10-02T05:36:19+00:00.

@dbalders dbalders left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

TritonAI Review

No actionable findings in the inspected changes; see coverage limits.

Reviewed 21557ab694b92c1d235b727ee3b217b9bc154146 against efc676978d70cbeb2ed544f22c677261ba5961db.

Inspected 2 file(s) in 2 part(s); reused 1 unchanged review part(s).

Changes

  • packages/contracts/src/orchestration.mobile-runtime.test.ts: This part adds packages/contracts/src/orchestration.mobile-runtime.test.ts, a regression test for the Hermes startup crash. It stubs a Segmenter-less Intl via vi.stubGlobal, resets the module registry, dynamically imports the full contracts barrel (./index.ts, which re-exports orchestration.ts), and asserts that ProjectIconOverride decodes valid monograms ("T3", combining-mark and Devanagari cluster forms including a ZWJ sequence) and rejects three-cluster inputs, restoring globals and modules in afterEach.
  • packages/contracts/src/orchestration.ts: This part replaces the module-scope Intl.Segmenter instance (which crashed on Hermes at app startup) in packages/contracts/src/orchestration.ts with a runtime-independent grapheme-cluster approximation (GRAPHEME_CONTINUATION regex plus countGraphemes), and rewires the ProjectMonogramText ≤2-grapheme filter to use it. The pattern check on line 573 is unchanged context. The other changed file, packages/contracts/src/orchestration.mobile-runtime.test.ts, is reviewed in a separate part.

Coverage limits

  • packages/contracts/src/orchestration.ts: Some candidate findings were omitted because their evidence or location could not be verified.
  • packages/contracts/src/orchestration.ts: The PR claims this is an exact cherry-pick of upstream #11984; the upstream patch content was not available, so whether the ZWJ and Hangul divergences originate upstream or were introduced downstream could not be verified. If they are upstream's exact code, the mergeability guidance weighs against diverging in this file; the correctness analysis above still applies.
  • packages/contracts/src/orchestration.ts: The producer paths for ProjectMonogramText (the server handler that accepts project icon updates and any client UI that constrains monogram entry) are not in the supplied context, so the P2 findings' exploitability via normal UI versus raw RPC could not be confirmed; the schema-level divergence from the previous Intl.Segmenter behavior stands regardless. This does not cover packages/contracts/src/orchestration.mobile-runtime.test.ts, whose changed lines are handled by a separate part.

CI snapshot

Combined commit status: pending
- status CodeRabbit: pending Review in progress
Check runs:
- Sync PR size label definitions: status=completed conclusion=skipped app=
- Label PR size: status=completed conclusion=success app=
- Mobile Native Static Analysis: status=completed conclusion=skipped app=
- Macroscope - Effect Service Conventions: status=completed conclusion=skipped app=
- Macroscope - Correctness Check: status=completed conclusion=skipped app=
- Prepare PR size config: status=completed conclusion=success app=
- Mobile Native Changes: status=completed conclusion=success app=
- Check: status=in_progress conclusion=none app=
- Test Server 1: status=completed conclusion=success app=
- Test Server 2: status=in_progress conclusion=none app=
- Release Smoke: status=completed conclusion=success app=
- Rust: status=completed conclusion=success app=
- Test Server 3: status=in_progress conclusion=none app=
- Test: status=in_progress conclusion=none app=
- Native fingerprint diff: status=completed conclusion=success app=

CI is reported separately. This code review does not claim to have run tests.

Comment /triton-review to request a fresh review. Findings are advisory; GitHub branch protection remains authoritative.

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 13.6 KiB +1 B (+0.0%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +3 B (+0.0%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB −2 B (−0.0%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 8 8 0 (0.0%) 21 ✅
Claude Total thread wire 13.6 KiB 13.7 KiB +31 B (+0.2%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB −4 B (−0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.5 KiB 6.6 KiB +35 B (+0.5%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.8 KiB 57.9 KiB +44 B (+0.1%) 66.4 KiB ✅
Claude Live turn messages 9 10 +1 (+11.1%) 21 ✅

Baseline: efc6769 · PR result: 3445a3b · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 114.0 KiB
  • Claude decoded thread snapshot: 114.7 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@dbalders dbalders left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

TritonAI Review

No actionable findings in the inspected changes; see coverage limits.

Reviewed 3445a3b6fd04a090b5bcb8ae756b80ab6c89fdba against efc676978d70cbeb2ed544f22c677261ba5961db.

Inspected 4 file(s) in 4 part(s); reused 3 unchanged review part(s).

Changes

  • packages/contracts/package.json: This part adds a pinned runtime dependency, unicode-segmenter 0.17.3, to @​t3tools/contracts (the shared contracts package that src/index.ts re-exports barrel-wide and that packages/contracts/src/orchestration.ts will use for grapheme counting in place of Intl.Segmenter). No other lines in this file change; the entry is added under dependencies alongside effect: catalog:.
  • packages/contracts/src/orchestration.mobile-runtime.test.ts: This part adds a new contracts test file, packages/contracts/src/orchestration.mobile-runtime.test.ts, which stubs the global Intl object so Intl.Segmenter is undefined, resets the module registry, re-imports the contracts entry point, and exercises ProjectIconOverride decoding: rejecting three visible letters separated by ZWJ/ZWNJ joiners, accepting persisted international monograms (Indic conjunct, decomposed Hangul, Thai/Lao), and accepting/rejecting a matrix of two-grapheme and three-grapheme inputs. afterEach restores the stubbed globals and resets modules. The stubbing and dynamic-import ordering are correct (stub before resetModules before import), the chosen inputs are consistent with UAX #29 grapheme segmentation rules that the production schema in orchestration.ts relies on (countGraphemes from unicode-segmenter), and the test does not leak the stub across tests because afterEach unconditionally calls vi.unstubAllGlobals(). No correctness, concurrency, security, or data-loss defects were found in the changed lines of this part.
  • packages/contracts/src/orchestration.ts: This part of packages/contracts/src/orchestration.ts removes the module-scope new Intl.Segmenter(...) construction (which crashed Hermes-based mobile clients at launch) and replaces the grapheme-count filter on ProjectMonogramText with countGraphemes from the unicode-segmenter/grapheme subpath, imported at module scope. A comment explains the Hermes motivation and the intent to keep segmentation identical across runtimes. The pattern, max-length, and surrounding ProjectIconOverride shapes are unchanged. The package.json, lockfile, and the new orchestration.mobile-runtime.test.ts are handled by other diff parts.
  • pnpm-lock.yaml: This lockfile part adds the unicode-segmenter dependency pinned at exactly 0.17.3 for packages/contracts. All three required lockfile locations are updated consistently: the importer entry under packages/contracts dependencies (specifier 0.17.3, resolved version 0.17.3), the package resolution entry with an integrity hash, and the snapshot entry unicode-segmenter@​0.17.3: {} showing no transitive dependencies, which matches the PR description's claim that the package has zero dependencies. The remaining changed lines are context records (@​effect/vitest, vite-plus, unified, unist-util-*, etc.) that are unchanged and only shown to anchor the new entries alphabetically. The entry format and placement match the pnpm lockfileVersion 9.0 layout used elsewhere in the file, and the exact-pin specifier matches the corresponding packages/contracts/package.json change reviewed in another part.

Coverage limits

  • packages/contracts/package.json: I cannot verify the contents or layout of the unicode-segmenter package at version 0.17.3 from the supplied context. The upstream unicode-segmenter project distributes WASM-backed segmentation; if 0.17.3 loads a .wasm asset at module scope, that would interact with (a) Hermes' limited/experimental WebAssembly support on the mobile runtime this PR exists to fix, (b) the CLI bundler, whose external/bundling policy in scripts/lib/cli-external-packages.ts is prefix-based and would treat unicode-segmenter as an ordinary bundled dependency, potentially breaking on an unhandled new URL('*.wasm') asset import, and (c) any Edge/browser runtime that imports the contracts barrel via src/index.ts. The PR description states that a signed iOS build launched and that tests run with Intl.Segmenter deleted, which is evidence against a Hermes-side failure, but Node tests would not surface a WASM-asset bundling problem in the CLI. The pnpm-lock.yaml record
  • packages/contracts/src/orchestration.mobile-runtime.test.ts: This review covers only packages/contracts/src/orchestration.mobile-runtime.test.ts. The changed lines in packages/contracts/src/orchestration.ts (the ProjectMonogramText schema and unicode-segmenter import), packages/contracts/package.json (the new unicode-segmenter 0.17.3 dependency), and pnpm-lock.yaml (the corresponding lockfile entry, which is not present in the supplied lockfile excerpt) are handled by other review parts; their correctness and the exact grapheme-counting behavior of unicode-segmenter 0.17.3 against these test inputs could not be independently verified from the evidence supplied here.
  • packages/contracts/src/orchestration.ts: Some candidate findings were omitted because their evidence or location could not be verified.
  • packages/contracts/src/orchestration.ts: The implementation of the pinned dependency is not part of the repository or this diff, so the finding's central factual claim — that unicode-segmenter@​0.17.3 instantiates WebAssembly at import time and is therefore Hermes-incompatible — could not be confirmed against the actual package code. Manual inspection of the installed package (or an execution test under real Hermes) is required to confirm or dismiss the P1 finding. The package.json/lockfile pinning and the contents of packages/contracts/src/orchestration.mobile-runtime.test.ts are handled by other diff parts and were not relied on here.
  • pnpm-lock.yaml: The diff context does not include registry metadata, so the correctness of the sha512-hKZwqBjJDmqNrq1... integrity hash for unicode-segmenter@​0.17.3 and the existence/contents of the published 0.17.3 tarball cannot be independently verified here; a frozen-lockfile install in CI would fail loudly if the hash or version is wrong, so this is a verification gap rather than evidence of a defect. Whether unicode-segmenter/grapheme is a valid subpath export is reviewed with the packages/contracts/src/orchestration.ts part.

CI snapshot

Combined commit status: success
- status CodeRabbit: success Review rate limited
Check runs:
- Mobile Native Static Analysis: status=completed conclusion=skipped app=
- Build macOS Apple Silicon preview: status=completed conclusion=skipped app=
- Sync PR size label definitions: status=completed conclusion=skipped app=
- Label PR size: status=completed conclusion=success app=
- Macroscope - Effect Service Conventions: status=completed conclusion=skipped app=
- Publish anonymous download: status=completed conclusion=skipped app=
- Release Smoke: status=completed conclusion=success app=
- Mobile Native Changes: status=completed conclusion=success app=
- Test Server 3: status=completed conclusion=success app=
- Rust: status=completed conclusion=success app=
- Test Server 2: status=completed conclusion=success app=
- Test Server 1: status=completed conclusion=success app=
- Test: status=in_progress conclusion=none app=
- Remove preview download: status=completed conclusion=skipped app=
- Check: status=in_progress conclusion=none app=
- Deploy web preview: status=completed conclusion=skipped app=
- Native fingerprint diff: status=completed conclusion=success app=
- Prepare PR size config: status=completed conclusion=success app=
- Macroscope - Correctness Check: status=completed conclusion=skipped app=

CI is reported separately. This code review does not claim to have run tests.

Comment /triton-review to request a fresh review. Findings are advisory; GitHub branch protection remains authoritative.

@dbalders
dbalders merged commit 49acb92 into main Oct 2, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 effective changed lines (test files excluded in mixed PRs).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants