mirror of
https://github.com/github/spec-kit.git
synced 2026-08-15 14:02:04 +02:00
fix(extensions): start fresh on a non-UTF-8 extension registry (#3998)
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 #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 commit is contained in:
co-authored by
Claude Opus 4.8
parent
f31b2b45eb
commit
3d4f71c90e
@@ -660,8 +660,13 @@ class ExtensionRegistry:
|
||||
if not isinstance(data.get("extensions"), dict):
|
||||
data["extensions"] = {}
|
||||
return data
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
# Corrupted or missing registry, start fresh
|
||||
except (json.JSONDecodeError, UnicodeDecodeError, FileNotFoundError):
|
||||
# Corrupted or missing registry, start fresh. A registry whose
|
||||
# bytes cannot be decoded as UTF-8 is the same corruption class as
|
||||
# malformed JSON — only the exception type differs, and it is
|
||||
# raised by the text-mode read before JSON parsing begins. OSError
|
||||
# is deliberately not caught: the data may be intact on disk, and
|
||||
# starting fresh would let a later _save() wipe it.
|
||||
return {"schema_version": self.SCHEMA_VERSION, "extensions": {}}
|
||||
|
||||
def _save(self):
|
||||
@@ -4310,11 +4315,10 @@ class ConfigManager:
|
||||
Returns an empty list if the registry is missing or corrupted
|
||||
(fresh project, ad-hoc test harness) so ``_get_env_config`` degrades
|
||||
to its pre-fix behaviour rather than crashing. ``UnicodeError`` is
|
||||
caught alongside ``OSError`` because ``ExtensionRegistry._load()``
|
||||
opens the file in text mode and only handles ``JSONDecodeError`` /
|
||||
``FileNotFoundError``, so a registry file with non-UTF-8 bytes would
|
||||
otherwise surface a ``UnicodeDecodeError`` here and break *every*
|
||||
config read instead of degrading gracefully.
|
||||
kept alongside ``OSError`` as belt-and-braces: ``_load()`` now starts
|
||||
fresh on non-UTF-8 registry bytes itself, but catching it here too
|
||||
keeps this call site degrading gracefully rather than breaking *every*
|
||||
config read if that handling ever regresses.
|
||||
|
||||
Used by ``_get_env_config`` to detect env vars whose remainder claims
|
||||
a longer, sibling-owned prefix (e.g. ``SPECKIT_GIT_HOOKS_URL`` is
|
||||
|
||||
Reference in New Issue
Block a user