From e7343b9f3fc77da45fc4f03d4f54ccbcd5a6872b Mon Sep 17 00:00:00 2001 From: marcelsafin <179933638+marcelsafin@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:25:45 +0200 Subject: [PATCH] fix(events): ignore non-UTF-8 event overrides Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/specify_cli/events.py | 6 ++++-- tests/integrations/test_events.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/events.py b/src/specify_cli/events.py index 98e49aee36..26bbaaa0f2 100644 --- a/src/specify_cli/events.py +++ b/src/specify_cli/events.py @@ -737,8 +737,10 @@ def resolve_events( if override_file.exists(): try: override = yaml.safe_load(override_file.read_text(encoding="utf-8")) or {} - except yaml.YAMLError: - logger.warning("Could not parse %s; ignoring override", override_file) + except (OSError, UnicodeError, yaml.YAMLError): + logger.warning( + "Could not read or parse %s; ignoring override", override_file + ) override = {} integrations = override.get("integrations", {}) if isinstance(override, dict) else {} if isinstance(integrations, dict) and integration_key in integrations: diff --git a/tests/integrations/test_events.py b/tests/integrations/test_events.py index 659ae48599..c9b9678c76 100644 --- a/tests/integrations/test_events.py +++ b/tests/integrations/test_events.py @@ -128,6 +128,23 @@ def test_layer2_empty_events_disables(self, tmp_path): ) assert result == {} + def test_unreadable_yaml_override_keeps_prior_layers(self, tmp_path): + """An unreadable override is ignored like malformed YAML.""" + override_file = tmp_path / ".specify" / "integration-events.yml" + override_file.parent.mkdir(parents=True, exist_ok=True) + override_file.write_bytes(b"\xff\xfe") + + result = resolve_events( + "claude", + {"events": {"post_tool_use": {"command": "speckit.tdd.validate"}}}, + tmp_path, + None, + ) + + assert result == { + "post_tool_use": [{"command": "speckit.tdd.validate"}] + } + def test_no_config_no_events(self, tmp_path): """Safe fallback with empty config/options.""" result = resolve_events("claude", None, tmp_path, None)