diff --git a/backend/app/gateway/github/registry.py b/backend/app/gateway/github/registry.py index 1dd718924f..98d97e3478 100644 --- a/backend/app/gateway/github/registry.py +++ b/backend/app/gateway/github/registry.py @@ -95,7 +95,7 @@ def _build_index(agents: list[tuple[str, AgentConfig]]) -> _Registry: # it is here, so this branch is unreachable. Keep the # guard for type-checker happiness. continue - index.setdefault((binding.repo, event), []).append(GitHubAgentMatch(user_id=user_id, agent=cfg, trigger=resolved)) + index.setdefault((binding.repo.lower(), event), []).append(GitHubAgentMatch(user_id=user_id, agent=cfg, trigger=resolved)) return index @@ -145,4 +145,4 @@ def lookup_agents( and the pre-resolved trigger config for this specific event, so the caller does not need to walk the agent's ``bindings`` again. """ - return registry.get((repo, event), []) + return registry.get((repo.lower(), event), []) diff --git a/backend/tests/test_github_dispatcher.py b/backend/tests/test_github_dispatcher.py index 3e24c147ca..42291dbb97 100644 --- a/backend/tests/test_github_dispatcher.py +++ b/backend/tests/test_github_dispatcher.py @@ -47,6 +47,34 @@ async def _drain(bus: MessageBus) -> list[InboundMessage]: return out +@pytest.mark.asyncio +@pytest.mark.parametrize("configured_repo, payload_repo", [("Acme/Widget", "acme/widget"), ("acme/widget", "Acme/Widget")]) +async def test_fanout_matches_repository_case_insensitively(base_dir: Path, configured_repo: str, payload_repo: str) -> None: + """GitHub repository names identify the same repository regardless of case.""" + _write_agent( + base_dir, + "default", + "reviewer", + {"name": "reviewer", "github": {"bindings": [{"repo": configured_repo, "triggers": {"issues": {"actions": ["opened"]}}}]}}, + ) + bus = MessageBus() + result = await fanout_event( + bus, + "issues", + "delivery-mixed-case", + { + "action": "opened", + "issue": {"number": 7, "user": {"login": "alice"}, "title": "Bug", "body": "Details"}, + "repository": {"full_name": payload_repo}, + "sender": {"login": "alice"}, + }, + ) + assert result["fired_agents"] == ["reviewer"] + messages = await _drain(bus) + assert len(messages) == 1 + assert messages[0].chat_id == payload_repo + + @pytest.mark.asyncio @pytest.mark.parametrize("event, object_key", [("issues", "issue"), ("pull_request", "pull_request")]) @pytest.mark.parametrize("author, sender, should_fire", [("owner", "other", False), ("other", "owner", True)])