Skip to content

Commit b30935a

Browse files
Merge pull request #61 from Contrast-Security-OSS/AIML-150_fix_finding_pr_for_copilot
Aiml 150 fix finding pr for copilot
2 parents 70d0b95 + 9d3048f commit b30935a

File tree

4 files changed

+217
-106
lines changed

4 files changed

+217
-106
lines changed

src/git_handler.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ def find_issue_with_label(label: str) -> int:
625625
return None
626626

627627

628-
def reset_issue(issue_number: int, remediation_label: str) -> bool:
628+
def reset_issue(issue_number: int, issue_title: str, remediation_label: str) -> bool:
629629
"""
630630
Resets a GitHub issue by:
631631
1. Removing all existing labels that start with "smartfix-id:"
@@ -650,7 +650,7 @@ def reset_issue(issue_number: int, remediation_label: str) -> bool:
650650
return False
651651

652652
# First check if there's an open PR for this issue
653-
open_pr = find_open_pr_for_issue(issue_number)
653+
open_pr = find_open_pr_for_issue(issue_number, issue_title)
654654
if open_pr:
655655
pr_number = open_pr.get("number")
656656
pr_url = open_pr.get("url")
@@ -759,7 +759,7 @@ def reset_issue(issue_number: int, remediation_label: str) -> bool:
759759
return False
760760

761761

762-
def find_open_pr_for_issue(issue_number: int) -> dict:
762+
def find_open_pr_for_issue(issue_number: int, issue_title: str) -> dict:
763763
"""
764764
Finds an open pull request associated with the given issue number.
765765
Specifically looks for PRs with branch names matching the pattern 'copilot/fix-{issue_number}'
@@ -805,8 +805,22 @@ def find_open_pr_for_issue(issue_number: int) -> dict:
805805
pr_list_output = run_command(claude_pr_list_command, env=gh_env, check=False)
806806

807807
if not pr_list_output or pr_list_output.strip() == "[]":
808-
debug_log(f"No open PRs found for issue #{issue_number} with either Copilot or Claude branch pattern")
809-
return None
808+
escaped_issue_title = issue_title.replace('"', '\\"')
809+
copilot_issue_title_search_pattern = f"in:title \"[WIP] {escaped_issue_title}\""
810+
copilot_issue_title_list_command = [
811+
"gh", "pr", "list",
812+
"--repo", config.GITHUB_REPOSITORY,
813+
"--state", "open",
814+
"--search", copilot_issue_title_search_pattern,
815+
"--limit", "1",
816+
"--json", "number,url,title,headRefName,baseRefName,state"
817+
]
818+
819+
pr_list_output = run_command(copilot_issue_title_list_command, env=gh_env, check=False)
820+
821+
if not pr_list_output or pr_list_output.strip() == "[]":
822+
debug_log(f"No open PRs found for issue #{issue_number} with either Copilot or Claude branch pattern")
823+
return None
810824

811825
prs_data = json.loads(pr_list_output)
812826

@@ -926,7 +940,7 @@ def get_issue_comments(issue_number: int, author: str = None) -> List[dict]:
926940
Returns:
927941
List[dict]: A list of comment data dictionaries or empty list if no comments or error
928942
"""
929-
author_log = f"and author: {author}" if author else ""
943+
author_log = f"and author: {author}" if author else ""
930944
debug_log(f"Getting comments for issue #{issue_number} {author_log}")
931945
gh_env = get_gh_env()
932946
author_filter = f"| map(select(.author.login == \"{author}\")) " if author else ""
@@ -1098,7 +1112,11 @@ def get_claude_workflow_run_id() -> int:
10981112
debug_log("Getting in-progress Claude workflow run ID")
10991113

11001114
gh_env = get_gh_env()
1101-
jq_filter = 'map(select(.event == "issues" or .event == "issue_comment") | select(.status == "in_progress") | select(.conclusion != "skipped")) | sort_by(.createdAt) | reverse | .[0]'
1115+
jq_filter = (
1116+
'map(select(.event == "issues" or .event == "issue_comment") | '
1117+
'select(.status == "in_progress") | select(.conclusion != "skipped")) | '
1118+
'sort_by(.createdAt) | reverse | .[0]'
1119+
)
11021120
workflow_command = [
11031121
"gh", "run", "list",
11041122
"--repo", config.GITHUB_REPOSITORY,

src/github/external_coding_agent.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def generate_fixes(self, vuln_uuid: str, remediation_id: str, vuln_title: str, i
183183
error_exit(remediation_id, FailureCategory.AGENT_FAILURE.value)
184184
else:
185185
debug_log(f"Found existing GitHub issue #{issue_number} with label {vulnerability_label}")
186-
if not git_handler.reset_issue(issue_number, remediation_label):
186+
if not git_handler.reset_issue(issue_number, issue_title, remediation_label):
187187
log(f"Failed to reset issue #{issue_number} with labels {vulnerability_label}, {remediation_label}", is_error=True)
188188
error_exit(remediation_id, FailureCategory.AGENT_FAILURE.value)
189189
is_existing_issue = True
@@ -193,10 +193,13 @@ def generate_fixes(self, vuln_uuid: str, remediation_id: str, vuln_title: str, i
193193
# Proceed with PR polling for all agent types
194194

195195
# Poll for PR creation by the external agent
196-
log(f"Waiting for external agent to create a PR for issue #{issue_number}")
196+
log(f"Waiting for external agent to create a PR for issue #{issue_number}, '{issue_title}'")
197197

198198
# Poll for a PR to be created by the external agent (100 attempts, 5 seconds apart = ~8.3 minutes max)
199-
pr_info = self._process_external_coding_agent_run(issue_number, remediation_id, vulnerability_label, remediation_label, is_existing_issue, max_attempts=100, sleep_seconds=5)
199+
pr_info = self._process_external_coding_agent_run(
200+
issue_number, issue_title, remediation_id, vulnerability_label,
201+
remediation_label, is_existing_issue, max_attempts=100, sleep_seconds=5
202+
)
200203

201204
log("\n::endgroup::")
202205
if pr_info:
@@ -215,7 +218,7 @@ def generate_fixes(self, vuln_uuid: str, remediation_id: str, vuln_title: str, i
215218
telemetry_handler.update_telemetry("resultInfo.failureCategory", FailureCategory.AGENT_FAILURE.name)
216219
return False
217220

218-
def _process_external_coding_agent_run(self, issue_number: int, remediation_id: str, vulnerability_label: str,
221+
def _process_external_coding_agent_run(self, issue_number: int, issue_title: str, remediation_id: str, vulnerability_label: str,
219222
remediation_label: str, is_existing_issue: bool,
220223
max_attempts: int = 100, sleep_seconds: int = 5) -> Optional[dict]:
221224
"""
@@ -246,7 +249,7 @@ def _process_external_coding_agent_run(self, issue_number: int, remediation_id:
246249
pr_info = self._process_claude_workflow_run(issue_number, remediation_id)
247250
else:
248251
# GitHub Copilot agent
249-
pr_info = git_handler.find_open_pr_for_issue(issue_number)
252+
pr_info = git_handler.find_open_pr_for_issue(issue_number, issue_title)
250253

251254
if pr_info:
252255
pr_number = pr_info.get("number")
@@ -288,7 +291,6 @@ def _process_external_coding_agent_run(self, issue_number: int, remediation_id:
288291
log(f"No PR found for issue #{issue_number} after {max_attempts} polling attempts", is_error=True)
289292
return None
290293

291-
292294
def _process_claude_workflow_run(self, issue_number: int, remediation_id: str,) -> Optional[dict]:
293295
"""
294296
Process the Claude Code workflow run and extract PR information from Claude's comment
@@ -307,13 +309,13 @@ def _process_claude_workflow_run(self, issue_number: int, remediation_id: str,)
307309

308310
if not workflow_run_id:
309311
# If no workflow run ID found yet, continue polling
310-
debug_log(f"Claude workflow_run_id not found, checking again...")
312+
debug_log("Claude workflow_run_id not found, checking again...")
311313
return None
312314

313315
# Get all issue comments to find the latest comment author.login
314316
issue_comments = git_handler.get_issue_comments(issue_number)
315317
if not issue_comments or len(issue_comments) == 0:
316-
debug_log(f"No comments added to issue, checking again...")
318+
debug_log("No comments added to issue, checking again...")
317319
return None
318320

319321
author_login = issue_comments[0].get("author", {}).get("login", '')
@@ -364,8 +366,8 @@ def _process_claude_workflow_run(self, issue_number: int, remediation_id: str,)
364366
debug_log(f"Claude create PR returned url: {pr_url}")
365367

366368
if not pr_url:
367-
log(f"Failed to create PR for Claude Code fix", is_error=True)
368-
reason = f"Could not create Claude PR due to processing issues"
369+
log("Failed to create PR for Claude Code fix", is_error=True)
370+
reason = "Could not create Claude PR due to processing issues"
369371
self._update_telemetry_and_exit_claude_agent_failure(reason, remediation_id, issue_number)
370372

371373
# Extract PR number from URL
@@ -396,8 +398,7 @@ def _process_claude_workflow_run(self, issue_number: int, remediation_id: str,)
396398
self._update_telemetry_and_exit_claude_agent_failure(msg, remediation_id, issue_number)
397399
return None
398400

399-
400-
def _process_claude_comment_body(self, comment_body: str, remediation_id: str, issue_number: int) -> dict:
401+
def _process_claude_comment_body(self, comment_body: str, remediation_id: str, issue_number: int) -> dict: # noqa: C901
401402
"""
402403
Process Claude's comment body to extract PR information. Returning the pr_title
403404
and pr_body are required for this method to be successful and to create the PR.
@@ -499,7 +500,6 @@ def _process_claude_comment_body(self, comment_body: str, remediation_id: str, i
499500
"pr_body": contrast_pr_body
500501
}
501502

502-
503503
def _get_claude_head_branch(self, head_branch_from_url: str,
504504
comment_body: str,
505505
issue_number: int,
@@ -547,8 +547,8 @@ def _get_claude_head_branch(self, head_branch_from_url: str,
547547

548548
# Final check - if no branch could be found by any method, fail gracefully
549549
if not head_branch:
550-
log(f"Could not determine claude branch name using any available method", is_error=True)
551-
reason = f"Could not extract Claude head_branch needed for PR creation"
550+
log("Could not determine claude branch name using any available method", is_error=True)
551+
reason = "Could not extract Claude head_branch needed for PR creation"
552552
self._update_telemetry_and_exit_claude_agent_failure(reason, remediation_id, issue_number)
553553

554554
return head_branch

0 commit comments

Comments
 (0)