From 14fab0a1af014af9a2a0bad71f6dcde23e9379d4 Mon Sep 17 00:00:00 2001 From: Marsel Safin <179933638+marcelsafin@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:33:39 +0200 Subject: [PATCH] fix(presets): tolerate non-UTF-8 legacy commands (#3896) Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/specify_cli/presets/__init__.py | 2 +- tests/test_presets.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/presets/__init__.py b/src/specify_cli/presets/__init__.py index 2f32d162b..4cc825b51 100644 --- a/src/specify_cli/presets/__init__.py +++ b/src/specify_cli/presets/__init__.py @@ -5297,7 +5297,7 @@ class PresetResolver: fm_strategy = fm_data.get("strategy") if isinstance(fm_strategy, str) and fm_strategy.lower() in VALID_PRESET_STRATEGIES: strategy = fm_strategy.lower() - except (yaml.YAMLError, OSError): + except (UnicodeDecodeError, yaml.YAMLError, OSError): # Best-effort legacy frontmatter parsing: keep default # strategy ("replace") when content is unreadable/invalid. pass diff --git a/tests/test_presets.py b/tests/test_presets.py index d4c964c83..b7fad70ad 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -11626,6 +11626,24 @@ class TestResolveContent: class TestCollectAllLayers: """Test PresetResolver.collect_all_layers() method.""" + def test_non_utf8_legacy_command_keeps_replace_strategy(self, project_dir): + presets_dir = project_dir / ".specify" / "presets" + command_path = ( + presets_dir / "legacy-pack" / "commands" / "speckit.legacy.md" + ) + command_path.parent.mkdir(parents=True) + command_path.write_bytes(b"\xff\xfe") + PresetRegistry(presets_dir).add( + "legacy-pack", {"version": "1.0.0", "priority": 10} + ) + + layers = PresetResolver(project_dir).collect_all_layers( + "speckit.legacy", "command" + ) + + assert layers[0]["path"] == command_path + assert layers[0]["strategy"] == "replace" + def test_single_core_layer(self, project_dir): """Test collecting layers with only core template.""" resolver = PresetResolver(project_dir)