fix(extensions): start fresh on a non-UTF-8 extension registry - #3998
Open
Noor-ul-ain001 wants to merge 1 commit into
Open
fix(extensions): start fresh on a non-UTF-8 extension registry#3998Noor-ul-ain001 wants to merge 1 commit into
Noor-ul-ain001 wants to merge 1 commit into
Conversation
ExtensionRegistry._load() catches json.JSONDecodeError and FileNotFoundError to start fresh on a corrupted or missing registry, but a .registry file with invalid UTF-8 bytes raised UnicodeDecodeError from the text-mode read before JSON parsing began. Because the registry is loaded in __init__, that bare traceback broke every extension command -- `specify extension list` on such a project exits with a raw UnicodeDecodeError instead of the module's clean path. Catch UnicodeDecodeError in the same clause: undecodable bytes are the same corruption class as unparseable JSON, only the exception type differs. OSError stays uncaught on purpose -- the data may be intact on disk, and starting fresh would let a later _save() wipe it. This is the exact twin of the PresetRegistry._load() fix in github#3955; the two registries are parallel implementations and only the preset side was corrected. _get_installed_sibling_ids() already worked around this gap locally by catching UnicodeError at its own call site; its comment is updated to reflect that _load() now handles the case itself, with the local catch kept as belt-and-braces against regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ExtensionRegistry._load()treats a corrupted registry as "start fresh", but only for two exception types:A
.specify/extensions/.registryfile containing invalid UTF-8 bytes raisesUnicodeDecodeErrorfrom the text-mode read before JSON parsing begins, so it escapes that clause. Because the registry is loaded in__init__, the bare traceback breaks every extension command on the project:ExtensionRegistryis also constructed fromevents.pyand from two places inpresets/__init__.py, so the crash reaches beyond theextensioncommand group.Fix
Catch
UnicodeDecodeErrorin the same clause — undecodable bytes are the same corruption class as unparseable JSON, only the exception type differs.OSErrorstays uncaught on purpose: the data may be intact on disk, and starting fresh would let a later_save()wipe it (same fail-closed reasoning as the workflow catalog cache loader).After the fix the command degrades gracefully:
Relationship to #3955
This is the exact twin of the
PresetRegistry._load()fix merged in #3955. The two registries are parallel implementations of the same pattern and only the preset side was corrected there; this applies the identical one-line change plus rationale comment to the extension side.Notably,
_get_installed_sibling_ids()already documented and worked around this precise gap at its own call site:That comment is updated to reflect that
_load()now handles the case itself; the local catch is kept as belt-and-braces against regression.Testing
Added
test_load_starts_fresh_for_non_utf8_registry, mirroring the #3955 preset test: writes undecodable bytes to.registry, then asserts the registry starts fresh andlist()/is_installed()behave.Verified the test genuinely covers the fix — with
src/specify_cli/extensions/__init__.pyreverted it fails with the originalUnicodeDecodeError, and passes with the fix applied.pytest tests/test_extensions.py→ 489 passed.pytest tests/test_presets.py tests/integrations/test_events.py→ 678 passed. The only failures in either run are pre-existing symlink tests that require Windows elevation (WinError 1314) and fail identically on a clean checkout.ruff checkpasses on both changed files.🤖 Generated with Claude Code