From 4bdf7a0c5b6b8cf97e321e21170c2fbe8af29397 Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:21:40 +0100 Subject: [PATCH] fix(github): pass repo parameter to GHClient for explicit PR resolution (#413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gh CLI was failing with "Could not resolve to a PullRequest" error because it inferred the repository from git remotes instead of using an explicit repository. With multiple remotes configured, the wrong repo could be queried. This fix passes the repo parameter through the call chain and adds the -R flag to all PR-related gh commands, ensuring the correct repository is always queried regardless of git remote configuration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 --- .../runners/github/context_gatherer.py | 7 +++- apps/backend/runners/github/gh_client.py | 32 +++++++++++++++++++ apps/backend/runners/github/orchestrator.py | 5 ++- .../github/providers/github_provider.py | 1 + 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/backend/runners/github/context_gatherer.py b/apps/backend/runners/github/context_gatherer.py index 4a8b654f..087a959c 100644 --- a/apps/backend/runners/github/context_gatherer.py +++ b/apps/backend/runners/github/context_gatherer.py @@ -201,13 +201,15 @@ class PRContext: class PRContextGatherer: """Gathers all context needed for PR review BEFORE the AI starts.""" - def __init__(self, project_dir: Path, pr_number: int): + def __init__(self, project_dir: Path, pr_number: int, repo: str | None = None): self.project_dir = Path(project_dir) self.pr_number = pr_number + self.repo = repo self.gh_client = GHClient( project_dir=self.project_dir, default_timeout=30.0, max_retries=3, + repo=repo, ) async def gather(self) -> PRContext: @@ -953,14 +955,17 @@ class FollowupContextGatherer: project_dir: Path, pr_number: int, previous_review: PRReviewResult, # Forward reference + repo: str | None = None, ): self.project_dir = Path(project_dir) self.pr_number = pr_number self.previous_review = previous_review + self.repo = repo self.gh_client = GHClient( project_dir=self.project_dir, default_timeout=30.0, max_retries=3, + repo=repo, ) async def gather(self) -> FollowupReviewContext: diff --git a/apps/backend/runners/github/gh_client.py b/apps/backend/runners/github/gh_client.py index 9b08ff02..d6e7ddbd 100644 --- a/apps/backend/runners/github/gh_client.py +++ b/apps/backend/runners/github/gh_client.py @@ -84,6 +84,7 @@ class GHClient: default_timeout: float = 30.0, max_retries: int = 3, enable_rate_limiting: bool = True, + repo: str | None = None, ): """ Initialize GitHub CLI client. @@ -93,11 +94,14 @@ class GHClient: default_timeout: Default timeout in seconds for commands max_retries: Maximum number of retry attempts enable_rate_limiting: Whether to enforce rate limiting (default: True) + repo: Repository in 'owner/repo' format. If provided, uses -R flag + instead of inferring from git remotes. """ self.project_dir = Path(project_dir) self.default_timeout = default_timeout self.max_retries = max_retries self.enable_rate_limiting = enable_rate_limiting + self.repo = repo # Initialize rate limiter singleton if enable_rate_limiting: @@ -254,6 +258,28 @@ class GHClient: # Should never reach here, but for type safety raise GHCommandError(f"gh {args[0]} failed after {self.max_retries} attempts") + # ========================================================================= + # Helper methods + # ========================================================================= + + def _add_repo_flag(self, args: list[str]) -> list[str]: + """ + Add -R flag to command args if repo is configured. + + This ensures gh CLI uses the correct repository instead of + inferring from git remotes, which can fail with multiple remotes + or when working in worktrees. + + Args: + args: Command arguments list + + Returns: + Modified args list with -R flag if repo is set + """ + if self.repo: + return args + ["-R", self.repo] + return args + # ========================================================================= # Convenience methods for common gh commands # ========================================================================= @@ -295,6 +321,7 @@ class GHClient: "--json", ",".join(json_fields), ] + args = self._add_repo_flag(args) result = await self.run(args) return json.loads(result.stdout) @@ -334,6 +361,7 @@ class GHClient: "--json", ",".join(json_fields), ] + args = self._add_repo_flag(args) result = await self.run(args) return json.loads(result.stdout) @@ -352,6 +380,7 @@ class GHClient: PRTooLargeError: If PR exceeds GitHub's 20,000 line diff limit """ args = ["pr", "diff", str(pr_number)] + args = self._add_repo_flag(args) try: result = await self.run(args) return result.stdout @@ -396,6 +425,7 @@ class GHClient: args.append("--comment") args.extend(["--body", body]) + args = self._add_repo_flag(args) await self.run(args) return 0 # gh CLI doesn't return review ID @@ -574,6 +604,7 @@ class GHClient: args.extend(["--subject", commit_title]) if commit_message: args.extend(["--body", commit_message]) + args = self._add_repo_flag(args) await self.run(args) @@ -586,6 +617,7 @@ class GHClient: body: Comment body """ args = ["pr", "comment", str(pr_number), "--body", body] + args = self._add_repo_flag(args) await self.run(args) async def pr_get_assignees(self, pr_number: int) -> list[str]: diff --git a/apps/backend/runners/github/orchestrator.py b/apps/backend/runners/github/orchestrator.py index 3ec71724..a06c8bd1 100644 --- a/apps/backend/runners/github/orchestrator.py +++ b/apps/backend/runners/github/orchestrator.py @@ -129,6 +129,7 @@ class GitHubOrchestrator: default_timeout=30.0, max_retries=3, enable_rate_limiting=True, + repo=config.repo, ) # Initialize bot detector for preventing infinite loops @@ -300,7 +301,9 @@ class GitHubOrchestrator: try: # Gather PR context print("[DEBUG orchestrator] Creating context gatherer...", flush=True) - gatherer = PRContextGatherer(self.project_dir, pr_number) + gatherer = PRContextGatherer( + self.project_dir, pr_number, repo=self.config.repo + ) print("[DEBUG orchestrator] Gathering PR context...", flush=True) pr_context = await gatherer.gather() diff --git a/apps/backend/runners/github/providers/github_provider.py b/apps/backend/runners/github/providers/github_provider.py index 558d0fb2..190d3baf 100644 --- a/apps/backend/runners/github/providers/github_provider.py +++ b/apps/backend/runners/github/providers/github_provider.py @@ -56,6 +56,7 @@ class GitHubProvider: self._gh_client = GHClient( project_dir=project_dir, enable_rate_limiting=self.enable_rate_limiting, + repo=self._repo, ) @property