From d1ca1052f415f6b00e7aebe20640227fd4eefc75 Mon Sep 17 00:00:00 2001 From: Space-Banane Date: Sat, 23 May 2026 14:20:37 +0200 Subject: [PATCH] [fix]. Reply on unsupported @codex commands --- TODO.md | 2 +- src/gitea_codex_bot/main.py | 34 +++++++++----- src/gitea_codex_bot/services/commands.py | 15 ++++++ tests/test_commands.py | 10 +++- tests/test_webhook.py | 60 ++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 13 deletions(-) diff --git a/TODO.md b/TODO.md index 1dd1e95..f32156b 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ ## Open Items By Priority ### P0 (Critical) -- [ ] `BUG`: True isolated runner flow: clone/fetch/checkout PR branch inside the ephemeral container itself, not on host before prompt generation. +- [x] `BUG`: True isolated runner flow: clone/fetch/checkout PR branch inside the ephemeral container itself, not on host before prompt generation. - [x] `BUG`: Remove host-side fallback path for review execution, or gate it behind explicit `ALLOW_HOST_FALLBACK=false` by default so isolation cannot be bypassed silently. - [x] `BUG`: Enforce `.codex-review.yml` `enabled=false` at runtime (currently loaded but not enforced). - [x] `BUG`: Remove fix command support from runtime and command parsing. diff --git a/src/gitea_codex_bot/main.py b/src/gitea_codex_bot/main.py index 1b91cd5..26ad394 100644 --- a/src/gitea_codex_bot/main.py +++ b/src/gitea_codex_bot/main.py @@ -17,7 +17,7 @@ from sqlalchemy.orm import Session from gitea_codex_bot.config import Settings, get_settings from gitea_codex_bot.db import get_session from gitea_codex_bot.models import JobStatus, ReviewJob -from gitea_codex_bot.services.commands import parse_command +from gitea_codex_bot.services.commands import detect_prefixed_command, parse_command from gitea_codex_bot.services.gitea import GiteaClient from gitea_codex_bot.services.jobs import cooldown_remaining_seconds, enqueue_job, persist_webhook_event from gitea_codex_bot.services.repo_config import RepoReviewConfig, parse_repo_review_config_text @@ -421,9 +421,31 @@ async def gitea_webhook( if sender_username == settings.gitea_bot_username: return {"accepted": False, "reason": "bot comment ignored"} + if repo not in settings.allowed_repo_set: + logger.info( + "Webhook ignored: repo not in ALLOWED_REPOS repo=%s pr=%s comment_id=%s sender=%s", + repo, + pr_number, + comment_id, + sender_username, + ) + return {"accepted": False, "reason": "repo not allowed"} + comment_body = str(payload.get("comment", {}).get("body", "")).strip() parsed_command = parse_command(comment_body, aliases=settings.bot_command_aliases) if not parsed_command: + attempted_command = detect_prefixed_command(comment_body, aliases=settings.bot_command_aliases) + if attempted_command: + gitea = GiteaClient(settings) + if attempted_command == "fix": + gitea.post_issue_comment(repo, pr_number, "⚠️ `@codex fix` is no longer supported on this bot.") + return {"accepted": False, "reason": "unsupported command", "command": attempted_command} + gitea.post_issue_comment( + repo, + pr_number, + f"⚠️ Command `@codex {attempted_command}` is not supported. Try `@codex -h`.", + ) + return {"accepted": False, "reason": "unsupported command", "command": attempted_command} logger.info( "Webhook ignored: no @codex review command repo=%s pr=%s comment_id=%s sender=%s", repo, @@ -442,16 +464,6 @@ async def gitea_webhook( parsed_command.name, ) - if repo not in settings.allowed_repo_set: - logger.info( - "Webhook ignored: repo not in ALLOWED_REPOS repo=%s pr=%s comment_id=%s sender=%s", - repo, - pr_number, - comment_id, - sender_username, - ) - return {"accepted": False, "reason": "repo not allowed"} - inserted = persist_webhook_event( session, delivery_id=x_gitea_delivery, diff --git a/src/gitea_codex_bot/services/commands.py b/src/gitea_codex_bot/services/commands.py index 4229046..2975f8b 100644 --- a/src/gitea_codex_bot/services/commands.py +++ b/src/gitea_codex_bot/services/commands.py @@ -10,6 +10,21 @@ HELP_ALIASES = {"-h", "--help", "help"} SUPPORTED_COMMANDS = {"review", "explain", "ignore", "rerun"} +def detect_prefixed_command(body: str, aliases: Iterable[str] | None = None) -> str | None: + stripped = body.strip() + match = PREFIX_RE.match(stripped) + if not match: + return None + command_alias = match.group(1).lstrip("@").lower() + allowed_aliases = {alias.lstrip("@").lower() for alias in (aliases or {"codex"})} + if command_alias not in allowed_aliases: + return None + remainder = match.group(2).strip() + if not remainder: + return None + return remainder.split(maxsplit=1)[0].lower() + + def parse_command(body: str, aliases: Iterable[str] | None = None) -> ParsedCommand | None: stripped = body.strip() match = PREFIX_RE.match(stripped) diff --git a/tests/test_commands.py b/tests/test_commands.py index 20a07d2..57290a3 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,4 +1,4 @@ -from gitea_codex_bot.services.commands import parse_command +from gitea_codex_bot.services.commands import detect_prefixed_command, parse_command def test_parse_review_command_modes() -> None: @@ -49,3 +49,11 @@ def test_parse_help_long_flag_and_arguments() -> None: assert cmd is not None assert cmd.name == "help" assert cmd.arguments == ["status", "quick"] + + +def test_detect_prefixed_command_for_unsupported_name() -> None: + assert detect_prefixed_command("@codex shipit now", aliases={"codex"}) == "shipit" + + +def test_detect_prefixed_command_returns_none_for_non_alias() -> None: + assert detect_prefixed_command("@someone review", aliases={"codex"}) is None diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 943dfd8..8577a44 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -254,6 +254,66 @@ def test_webhook_accepts_help_short_flag_and_queues(monkeypatch) -> None: assert queued.command == "help" +def test_webhook_replies_fix_is_no_longer_supported(monkeypatch) -> None: + posted_comments: list[str] = [] + monkeypatch.setattr( + "gitea_codex_bot.services.gitea.GiteaClient.post_issue_comment", + lambda _self, _repo, _pr, body: posted_comments.append(body) or 100, + ) + + client = TestClient(app) + payload_obj = _payload("@codex fix --branch", username="alice", comment_id=444) + raw = json.dumps(payload_obj).encode() + + response = client.post( + "/webhook/gitea", + content=raw, + headers={ + "X-Gitea-Event": "issue_comment", + "X-Gitea-Delivery": "d-fix-unsupported", + "X-Gitea-Signature": _sign(raw), + "Content-Type": "application/json", + }, + ) + + assert response.status_code == 200 + assert response.json()["reason"] == "unsupported command" + assert response.json()["command"] == "fix" + assert any("no longer supported" in body for body in posted_comments) + session_factory = get_session_factory() + with session_factory() as session: + queued = session.execute(select(ReviewJob).where(ReviewJob.trigger_comment_id == 444)).scalar_one_or_none() + assert queued is None + + +def test_webhook_replies_for_unknown_prefixed_command(monkeypatch) -> None: + posted_comments: list[str] = [] + monkeypatch.setattr( + "gitea_codex_bot.services.gitea.GiteaClient.post_issue_comment", + lambda _self, _repo, _pr, body: posted_comments.append(body) or 100, + ) + + client = TestClient(app) + payload_obj = _payload("@codex deploy", username="alice", comment_id=445) + raw = json.dumps(payload_obj).encode() + + response = client.post( + "/webhook/gitea", + content=raw, + headers={ + "X-Gitea-Event": "issue_comment", + "X-Gitea-Delivery": "d-unknown-unsupported", + "X-Gitea-Signature": _sign(raw), + "Content-Type": "application/json", + }, + ) + + assert response.status_code == 200 + assert response.json()["reason"] == "unsupported command" + assert response.json()["command"] == "deploy" + assert any("not supported" in body for body in posted_comments) + + def test_webhook_logs_when_repo_not_allowed(monkeypatch) -> None: messages: list[str] = []