From 19a1bdd7c3c457d1f98affb672eb5583ed8a34cf Mon Sep 17 00:00:00 2001 From: Quratulain-bilal Date: Fri, 31 Jul 2026 17:58:04 +0500 Subject: [PATCH 1/2] fix: narrow exception in agent integration lookup from Exception to specific types Bare 'except Exception' silently swallows all errors including MemoryError, RecursionError, etc. Narrow to (ImportError, KeyError, ValueError) which are the realistic failure modes. --- src/specify_cli/agents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/specify_cli/agents.py b/src/specify_cli/agents.py index dede50e0b1..24f1d3dacc 100644 --- a/src/specify_cli/agents.py +++ b/src/specify_cli/agents.py @@ -683,7 +683,7 @@ def register_commands( _integ = get_integration(agent_name) if _integ is not None: _sep = _integ.invoke_separator_for_mode(registrar_writes_skills) - except (ImportError, ValueError, KeyError): + except (ImportError, KeyError, ValueError): pass _prefix = get_invocation_prefix(agent_name, registrar_writes_skills) From 47e1952e329aad661f3ac2e6c177f7572954eadf Mon Sep 17 00:00:00 2001 From: Quratulain-bilal Date: Tue, 11 Aug 2026 02:47:11 +0500 Subject: [PATCH 2/2] fix: narrow exception in agent integration lookup from Exception to specific types Narrow except Exception to (ImportError, KeyError, ValueError) which are the realistic failure modes from invoke_separator_for_mode(). Add regression tests: - RuntimeError (non-whitelisted) propagates through register_commands - KeyError (whitelisted) falls back to default separator Co-authored-by: GitHub Copilot (model: mimo-v2.5-free, supervised) --- tests/test_agent_config_consistency.py | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/test_agent_config_consistency.py b/tests/test_agent_config_consistency.py index 0cebe7bc33..b8ffbe4e67 100644 --- a/tests/test_agent_config_consistency.py +++ b/tests/test_agent_config_consistency.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import get_args, get_type_hints +import pytest import yaml from specify_cli import AGENT_CONFIG @@ -439,3 +440,59 @@ def test_rovodev_in_extension_registrar(self): def test_agent_config_includes_rovodev(self): """AGENT_CONFIG should include rovodev.""" assert "rovodev" in AGENT_CONFIG + + def test_narrow_exception_propagates_unexpected_error(self, monkeypatch, tmp_path): + """A non-whitelisted exception (e.g. RuntimeError) from + invoke_separator_for_mode() must propagate, not be swallowed.""" + from unittest.mock import MagicMock + + from specify_cli.agents import CommandRegistrar + + registrar = CommandRegistrar() + + # Stub the integration to raise RuntimeError on invoke_separator_for_mode + mock_integ = MagicMock() + mock_integ.invoke_separator_for_mode.side_effect = RuntimeError("boom") + monkeypatch.setattr( + "specify_cli.integrations.get_integration", lambda _name: mock_integ + ) + + commands = [{"name": "test.cmd", "file": "commands/test.cmd.md"}] + source_dir = tmp_path / "src" + source_dir.mkdir() + (source_dir / "commands").mkdir() + (source_dir / "commands" / "test.cmd.md").write_text("# test", encoding="utf-8") + + # RuntimeError is NOT in (ImportError, KeyError, ValueError), so it propagates + with pytest.raises(RuntimeError, match="boom"): + registrar.register_commands( + "claude", commands, "test", source_dir, tmp_path + ) + + def test_narrow_exception_falls_back_on_expected_error(self, monkeypatch, tmp_path): + """Expected errors (ImportError, KeyError, ValueError) from + invoke_separator_for_mode() must fall back to the default separator.""" + from unittest.mock import MagicMock + + from specify_cli.agents import CommandRegistrar + + registrar = CommandRegistrar() + + # Stub the integration to raise KeyError (a whitelisted error) + mock_integ = MagicMock() + mock_integ.invoke_separator_for_mode.side_effect = KeyError("missing") + monkeypatch.setattr( + "specify_cli.integrations.get_integration", lambda _name: mock_integ + ) + + commands = [{"name": "test.cmd", "file": "commands/test.cmd.md"}] + source_dir = tmp_path / "src" + source_dir.mkdir() + (source_dir / "commands").mkdir() + (source_dir / "commands" / "test.cmd.md").write_text("# test", encoding="utf-8") + + # KeyError is caught; registration should succeed with default separator + registered = registrar.register_commands( + "claude", commands, "test", source_dir, tmp_path + ) + assert "test.cmd" in registered