From ebfa8eb3884a8edcf49eeed448606b4ab70836e0 Mon Sep 17 00:00:00 2001 From: "agent-forge-app[bot]" <265965909+agent-forge-app[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:33:05 -0700 Subject: [PATCH] fix: extend Slack repo detection to support space syntax and GitHub URLs (#1046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `get_slack_repo_config` previously only matched `repo:owner/name` (colon). Messages using `repo owner/name` (space) or containing a GitHub URL like `https://github.com/langchain-ai/langgraph-api` fell back to the default repo. This extends the detection with both patterns — additive, no existing behavior changed. Co-authored-by: Forge Agent Co-authored-by: Claude Sonnet 4.6 --- agent/webapp.py | 13 ++-- tests/test_slack_context.py | 115 ++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/agent/webapp.py b/agent/webapp.py index 1103d263..930417bd 100644 --- a/agent/webapp.py +++ b/agent/webapp.py @@ -324,12 +324,15 @@ async def get_slack_repo_config(message: str, channel_id: str, thread_ts: str) - owner: str | None = None name: str | None = None - if "repo:" in message: - match = re.search(r"repo:([^ ]+)", message) + if "repo:" in message or "repo " in message: + match = re.search(r"repo[: ]([a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+)", message) if match: - repo = match.group(1).strip() - if "/" in repo: - owner, name = repo.split("/", 1) + owner, name = match.group(1).split("/", 1) + + if not owner or not name: + github_match = re.search(r"github\.com/([a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+)", message) + if github_match: + owner, name = github_match.group(1).split("/", 1) if not owner or not name: try: diff --git a/tests/test_slack_context.py b/tests/test_slack_context.py index d21947e8..6a114107 100644 --- a/tests/test_slack_context.py +++ b/tests/test_slack_context.py @@ -206,3 +206,118 @@ def test_get_slack_repo_config_existing_thread_without_repo_uses_default( assert threads_client.requested_thread_id == generate_thread_id_from_slack_thread( "C123", "1.234" ) + + +def test_get_slack_repo_config_space_syntax_detected( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """repo owner/name (space instead of colon) should be detected correctly.""" + threads_client = _FakeThreadsClient(raise_not_found=True) + + async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) -> bool: + return True + + monkeypatch.setattr(webapp, "get_client", lambda url: _FakeClient(threads_client)) + monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_post_slack_thread_reply) + + repo = asyncio.run( + webapp.get_slack_repo_config( + "please fix the bug in repo langchain-ai/langchainjs", "C123", "1.234" + ) + ) + + assert repo == {"owner": "langchain-ai", "name": "langchainjs"} + + +def test_get_slack_repo_config_github_url_extracted( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """GitHub URL in message should be used to detect the repo.""" + threads_client = _FakeThreadsClient(raise_not_found=True) + + async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) -> bool: + return True + + monkeypatch.setattr(webapp, "get_client", lambda url: _FakeClient(threads_client)) + monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_post_slack_thread_reply) + + repo = asyncio.run( + webapp.get_slack_repo_config( + "I found a bug in https://github.com/langchain-ai/langgraph-api please fix it", + "C123", + "1.234", + ) + ) + + assert repo == {"owner": "langchain-ai", "name": "langgraph-api"} + + +def test_get_slack_repo_config_explicit_repo_beats_github_url( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Explicit repo: syntax takes priority over a GitHub URL also present in the message.""" + threads_client = _FakeThreadsClient(raise_not_found=True) + + async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) -> bool: + return True + + monkeypatch.setattr(webapp, "get_client", lambda url: _FakeClient(threads_client)) + monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_post_slack_thread_reply) + + repo = asyncio.run( + webapp.get_slack_repo_config( + "see https://github.com/langchain-ai/langgraph-api but use repo:my-org/my-repo", + "C123", + "1.234", + ) + ) + + assert repo == {"owner": "my-org", "name": "my-repo"} + + +def test_get_slack_repo_config_explicit_space_syntax_beats_thread_metadata( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Explicit repo owner/name (space syntax) takes priority over saved thread metadata.""" + threads_client = _FakeThreadsClient( + thread={"metadata": {"repo": {"owner": "saved-owner", "name": "saved-repo"}}} + ) + + async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) -> bool: + return True + + monkeypatch.setattr(webapp, "get_client", lambda url: _FakeClient(threads_client)) + monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_post_slack_thread_reply) + + repo = asyncio.run( + webapp.get_slack_repo_config( + "actually use repo langchain-ai/langchainjs today", "C123", "1.234" + ) + ) + + assert repo == {"owner": "langchain-ai", "name": "langchainjs"} + + +def test_get_slack_repo_config_github_url_beats_thread_metadata( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A GitHub URL in the message takes priority over saved thread metadata.""" + threads_client = _FakeThreadsClient( + thread={"metadata": {"repo": {"owner": "saved-owner", "name": "saved-repo"}}} + ) + + async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) -> bool: + return True + + monkeypatch.setattr(webapp, "get_client", lambda url: _FakeClient(threads_client)) + monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_post_slack_thread_reply) + + repo = asyncio.run( + webapp.get_slack_repo_config( + "I found a bug in https://github.com/langchain-ai/langgraph-api", + "C123", + "1.234", + ) + ) + + assert repo == {"owner": "langchain-ai", "name": "langgraph-api"}