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