pi-intl-segmenter-fallback

pi extension that stops the TUI from segfaulting on small-ICU Node builds (RHEL/Fedora nodejs without nodejs-full-i18n) by patching Intl.Segmenter with a pure-JS fallback

Packages

Package details

extension

Install pi-intl-segmenter-fallback from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-intl-segmenter-fallback
Package
pi-intl-segmenter-fallback
Version
0.1.0
Published
Jul 7, 2026
Downloads
41/mo · 20/wk
Author
t0msilver
License
MIT
Types
extension
Size
20.1 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-intl-segmenter-fallback

npm CI license: MIT

A pi extension that stops the TUI from segfaulting at startup on Node builds compiled with small-ICU — typically the RHEL/Fedora nodejs RPM installed without nodejs-full-i18n.

The problem

On small-ICU Node builds, new Intl.Segmenter() succeeds but the first .segment() call dereferences a null icu::BreakIterator inside V8 and kills the process with SIGSEGV (nodejs/node#51752). pi's TUI calls .segment() for grapheme-cluster cursor and width math on its very first render, so pi dies instantly on those builds (earendil-works/pi#6359).

Check whether your build is affected:

node -e 'console.log(process.config.variables.icu_small)'   # true → affected

The real fix is installing full ICU data (sudo dnf install nodejs-full-i18n). This extension is a stopgap that keeps pi usable when you can't — no root, immutable image, distro without the package.

Install

pi install npm:pi-intl-segmenter-fallback

Or straight from git, or pinned, or just for one run:

pi install git:github.com/T0mSIlver/pi-intl-segmenter-fallback
pi install npm:pi-intl-segmenter-fallback@0.1.0   # pinned, skipped by pi update
pi -e npm:pi-intl-segmenter-fallback              # try without installing

How it works

At extension import time, if process.config.variables.icu_small === true, the extension replaces Intl.Segmenter.prototype.segment and .resolvedOptions with a pure-JS fallback. On full-ICU/system-ICU builds it is a strict no-op — method identity is untouched (verified by the test suite).

It patches the prototype, not the constructor, because the constructor works even on broken builds — only .segment() crashes. pi's TUI creates its segmenter singletons at module load, before extensions run; the prototype patch covers those pre-existing instances, so the extension only needs to load before the first .segment() call (the first TUI render), which pi's extension loader guarantees. Granularity of pre-existing instances is recovered through the native resolvedOptions, which only reads internal slots and is safe on broken builds (verified on a real one).

When the fallback activates, a one-time warning is shown at session start.

Fallback semantics

Fine for a terminal UI, not linguistically exact:

Granularity Behavior
grapheme One segment per Unicode code point (surrogate-pair safe). ZWJ/skin-tone clusters split into parts — 👍🏽 takes two arrow presses instead of one; cursor math stays self-consistent because pi derives widths from the same segmentation.
word Runs of \p{L}\p{N}\p{M}\p{Pc} tagged isWordLike: true (what pi's Alt-B/Alt-F word navigation reads), runs of whitespace, other characters individually.
sentence Naive split after terminal punctuation (unused by pi).

Segments objects are re-iterable and support .containing(index); each segment is {segment, index, input[, isWordLike]}.

Environment overrides

Variable Effect
PI_SEGMENTER_FALLBACK=1 Force the patch on even with healthy ICU (testing)
PI_SEGMENTER_FALLBACK=0 Never patch, even on small-ICU builds

Test evidence

Verified against a real small-ICU build (AlmaLinux 9 nodejs-22.22.2 without nodejs-full-i18n): without the extension, pi segfaults at startup (exit 139); with it, the TUI renders, cursor movement across héllo 👍🏽 test is consistent, and word navigation works. On full-ICU Node 24 the extension provably changes nothing. Full transcripts and methodology: docs/test-evidence.md.

Development

npm install
npm run verify        # typecheck + test suite (needs Node ≥ 22.18)
pi install .          # smoke-test the package manifest against a local pi

The test suite (test/fallback.mjs) simulates pi's usage pattern — segmenter instances constructed before the extension imports — and asserts no-op behavior on healthy builds (noop mode) and full fallback semantics (forced mode).

License

MIT