diff --git a/src/specify_cli/events/__init__.py b/src/specify_cli/events/__init__.py index d8007b9f76..c397d980a9 100644 --- a/src/specify_cli/events/__init__.py +++ b/src/specify_cli/events/__init__.py @@ -997,7 +997,7 @@ def resolve_events( override_file, integration_key, ) else: - key_events = key_data.get("events", {}) + key_events = key_data.get("events") if not isinstance(key_events, dict): logger.warning( "Override %s: 'events' for '%s' is not a mapping; ignoring override", diff --git a/tests/specify_cli/events/test_events.py b/tests/specify_cli/events/test_events.py index a0d733d175..bd78cabd92 100644 --- a/tests/specify_cli/events/test_events.py +++ b/tests/specify_cli/events/test_events.py @@ -2988,6 +2988,30 @@ class TestOverridePreserveLayers: the accumulated built-in + extension layers, instead of disabling all hooks.""" + @pytest.mark.parametrize("entry", ["{}", "{evnts: {stop: {command: speckit.typo}}}"]) + def test_missing_events_field_keeps_prior_layers(self, tmp_path, caplog, entry): + ext_dir = tmp_path / ".specify" / "extensions" / "my-ext" + ext_dir.mkdir(parents=True) + (ext_dir / "extension.yml").write_text( + "events:\n session_start:\n command: speckit.my-ext.boot\n", + encoding="utf-8", + ) + override_file = tmp_path / ".specify" / "integration-events.yml" + override_file.write_text(f"integrations:\n claude: {entry}\n", encoding="utf-8") + + result = resolve_events( + "claude", + {"events": {"post_tool_use": {"command": "speckit.tdd.validate"}}}, + tmp_path, + None, + ) + + assert result == { + "post_tool_use": [{"command": "speckit.tdd.validate"}], + "session_start": [{"command": "speckit.my-ext.boot"}], + } + assert "'events' for 'claude' is not a mapping; ignoring override" in caplog.text + def test_invalid_override_entry_keeps_prior_layers(self, tmp_path): override_file = tmp_path / ".specify" / "integration-events.yml" override_file.parent.mkdir(parents=True, exist_ok=True)