Skip to content

fix(cli): preload rich's Unicode width table before in-place upgrade - #1318

Closed
groksrc wants to merge 1 commit into
mainfrom
fix/1316-preload-unicode-width-table
Closed

fix(cli): preload rich's Unicode width table before in-place upgrade#1318
groksrc wants to merge 1 commit into
mainfrom
fix/1316-preload-unicode-width-table

Conversation

@groksrc

@groksrc groksrc commented Aug 24, 2026

Copy link
Copy Markdown
Member

Fixes #1316.

The bug

bm update crashed with an unhandled traceback after Homebrew had already replaced the running installation:

ModuleNotFoundError: No module named 'rich._unicode_data.unicode17-0-0'

_preload_lazy_console_modules() (497a4e0) was written for exactly this failure mode, but it only preloads rich._emoji_codes and typer.rich_utils. rich also defers its Unicode cell-width table until the first character it cannot measure with the ASCII fast path in _cell_len — so the import lands after brew upgrade removed the prefix we are executing from, and the message meant to report the result becomes a traceback plus exit code 1 for an upgrade that had already succeeded.

Reported on 0.21.5 (which predates the preload entirely), but still reproducible on main — the added test fails without this change with that exact error.

Two details worth keeping in mind while reviewing, both counter-intuitive:

  • unicode17-0-0 is not a Python/Unicode mismatch. rich._unicode_data.load() defaults to "latest" (VERSIONS[-1]), not the interpreter's unidata_version, so it asks for 17.0.0 even on a py3.14 interpreter reporting Unicode 16.0.0. The file ships in rich; it just was not on disk anymore.
  • It only fires for non-ASCII text. "Basic Memory is up to date (0.23.0)." never touches the table. The FAILED branch echoing captured brew stderr — curly quotes, em dashes, — does.

Changes

1. Preload the width table (the actual fix)

from rich.cells import cell_len
cell_len("─‘✓")

Going through cell_len rather than importing rich._unicode_data.unicode<version> by name means rich picks the table version itself and honors UNICODE_VERSION exactly as the print path does — no hard-coded version to drift, and it works across the rich>=13.9.4 range in pyproject.toml (older rich has no _unicode_data package at all).

2. Make the status line unable to fail the command (defense in depth)

New print_update_status() falls back to a plain print() if the console raises, and all update status lines in commands/update.py and maybe_run_periodic_auto_update now go through it.

This is the second deferred import found this way, and rich/typer are free to add more. A preload list can only ever cover what we know about today; by the time these lines print, the upgrade has already succeeded, so a status line should never be what turns it into a traceback. Happy to drop this half if you would rather keep the change minimal.

Tests

  • test_status_message_survives_upgraded_away_install — simulates the deleted prefix with a meta_path finder that raises for any not-yet-imported module, then prints a long non-ASCII line (long and non-ASCII on purpose: that is what forces the wrap that reaches for the table). It calls console.print directly rather than print_update_status, so the fallback cannot mask a preload regression.
  • _cool_deferred_width_table() — clears rich's caches and unloads the table module first. Without this the test passes whether or not the fix is present, since earlier tests in the session warm the cache. Verified by neutralizing only the cell_len line: the test then fails with the reported ModuleNotFoundError, and passes with it restored.
  • test_print_update_status_falls_back_to_plain_output — covers the fallback path.

tests/cli/test_auto_update.py passes (33), ruff check/format clean, ty check shows no new diagnostics (the 4 pymilvus unresolved-import errors are present on main too).

Not addressed here

Noted in #1316 as separate observations: brew upgrade appears to have exited non-zero on an upgrade that landed (frame update:25 is the FAILED branch, yet 0.23.0 installed and the old Cellar directory was removed), which suggests a post-upgrade version re-check before classifying the result would be worthwhile.

`bm update` crashed with an unhandled ModuleNotFoundError after Homebrew
replaced the running installation on disk:

    ModuleNotFoundError: No module named 'rich._unicode_data.unicode17-0-0'

`_preload_lazy_console_modules` was added for exactly this failure mode,
but it only covered `rich._emoji_codes` and `typer.rich_utils`. rich also
defers its Unicode cell-width table until the first character it cannot
measure with the ASCII fast path in `_cell_len` -- and the status messages
echo captured `brew`/`uv` output, which carries curly quotes and em dashes.
That import lands after `brew upgrade` deleted the prefix we are running
from, so the message meant to report the result became a traceback and a
non-zero exit for an upgrade that had already succeeded.

Resolve the table through `rich.cells.cell_len` rather than importing a
module by name, so rich picks the version and honors UNICODE_VERSION the
same way the print path does.

Also route every update status line through `print_update_status`, which
falls back to a plain write if the console raises. The preload can only
cover the deferred imports known today; once the upgrade has succeeded, a
status line must never be what fails the command.

Fixes #1316

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112BNQpaC8AujnHc17YzS9D
@phernandez

Copy link
Copy Markdown
Member

Closing as landed: this fix is on main as 0751139 with your commit and authorship intact — it was bundled into the v0.23.1 fast-follow branch before this PR could be merged directly. It ships in v0.23.1 (releasing today) and is credited to you in the release. Thanks for the excellent diagnosis and fix — the meta_path-based regression test in particular. Sorry for the awkward duplicate-landing path.

@phernandez phernandez closed this Aug 25, 2026
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.

bm update: crash after in-place upgrade — preload misses rich's Unicode width table

2 participants