From 32b1bc239a6d0098c4f86447bc4119cc448aad20 Mon Sep 17 00:00:00 2001 From: SshauryaaRocks19 <198586011+SshauryaaRocks19@users.noreply.github.com> Date: Sun, 15 Mar 2026 00:16:47 +0530 Subject: [PATCH 1/4] Fix: Ensure webhook signature verification fails when no secret is configured. --- src/github_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github_api.py b/src/github_api.py index 7260e15..971e33b 100644 --- a/src/github_api.py +++ b/src/github_api.py @@ -806,7 +806,7 @@ async def verify_github_signature(request, payload_body, secret): if not secret: # If no secret is configured, skip verification (development mode) print("WARNING: Webhook secret not configured - skipping signature verification") - return True + return False signature_header = request.headers.get('x-hub-signature-256') if not signature_header: From 2f99a8fddfda4c15267a61c7a4088fc77b239441 Mon Sep 17 00:00:00 2001 From: SshauryaaRocks19 <198586011+SshauryaaRocks19@users.noreply.github.com> Date: Sun, 15 Mar 2026 00:30:07 +0530 Subject: [PATCH 2/4] Refactor GraphQL PR fetching to use variables and ensure only valid data is upserted. --- src/github_api.py | 79 ++++++++++++++++++++++++++--------------------- src/handlers.py | 2 +- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/github_api.py b/src/github_api.py index 971e33b..158bc76 100644 --- a/src/github_api.py +++ b/src/github_api.py @@ -376,14 +376,11 @@ async def fetch_multiple_prs_batch(prs_to_fetch, token=None): for batch_start in range(0, len(prs_to_fetch), MAX_PRS_PER_BATCH): batch = prs_to_fetch[batch_start:batch_start + MAX_PRS_PER_BATCH] - # Build GraphQL query with aliases for each PR - # We'll fetch essential PR data in one query query_parts = [] - for i, (owner, repo, pr_number) in enumerate(batch): - alias = f"pr{i}" - query_parts.append(f""" - {alias}: repository(owner: "{owner}", name: "{repo}") {{ - pullRequest(number: {pr_number}) {{ + var_decls = [] + variables = {} + + pr_fields = """ title state isDraft @@ -392,49 +389,62 @@ async def fetch_multiple_prs_batch(prs_to_fetch, token=None): mergeable mergeStateStatus changedFiles - commits {{ + commits { totalCount - }} - author {{ + } + author { login avatarUrl - }} - baseRepository {{ - owner {{ + } + baseRepository { + owner { avatarUrl - }} - }} + } + } headRefOid baseRefName headRefName - headRepository {{ - owner {{ + headRepository { + owner { login - }} - }} - reviewThreads(first: 100) {{ - nodes {{ + } + } + reviewThreads(first: 100) { + nodes { isResolved - }} - pageInfo {{ + } + pageInfo { hasNextPage - }} - }} - reviews(first: 100) {{ - nodes {{ + } + } + reviews(first: 100) { + nodes { state submittedAt - author {{ + author { login avatarUrl - }} - }} - }} - }} + } + } + } + """ + + for i, (owner, repo, pr_number) in enumerate(batch): + var_decls.extend([ + f"$owner{i}: String!", + f"$repo{i}: String!", + f"$pr{i}: Int!" + ]) + variables[f"owner{i}"] = owner + variables[f"repo{i}"] = repo + variables[f"pr{i}"] = pr_number + query_parts.append(f""" + pr{i}: repository(owner: $owner{i}, name: $repo{i}) {{ + pullRequest(number: $pr{i}) {{{pr_fields}}} }} """) - query = "query { " + " ".join(query_parts) + " }" + query = f"query({', '.join(var_decls)}) {{ {' '.join(query_parts)} }}" headers = { 'Accept': 'application/vnd.github+json', @@ -446,11 +456,10 @@ async def fetch_multiple_prs_batch(prs_to_fetch, token=None): headers['Authorization'] = f'Bearer {token}' try: - # Make GraphQL request options = to_js({ "method": "POST", "headers": headers, - "body": json.dumps({"query": query}) + "body": json.dumps({"query": query, "variables": variables}) }, dict_converter=Object.fromEntries) response = await fetch(graphql_url, options) diff --git a/src/handlers.py b/src/handlers.py index 57df693..5f68833 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -1077,7 +1077,7 @@ async def handle_github_webhook(request, env): # Fetch fresh PR data and add to tracking webhook_token = getattr(env, 'GITHUB_TOKEN', None) fetched_pr_data = await fetch_pr_data(repo_owner, repo_name, pr_number, webhook_token) - if fetched_pr_data: + if fetched_pr_data and not fetched_pr_data.get('not_found') and not fetched_pr_data.get('not_modified'): await upsert_pr(db, pr_url, repo_owner, repo_name, pr_number, fetched_pr_data) # Get the newly created PR ID From 832382d6bcab437998c84edd14a9f9f1c5a0af8f Mon Sep 17 00:00:00 2001 From: SshauryaaRocks19 <198586011+SshauryaaRocks19@users.noreply.github.com> Date: Sun, 15 Mar 2026 01:17:11 +0530 Subject: [PATCH 3/4] Fix: Prevent PR data upsert and cache invalidation when fetched data indicates not found or not modified. --- src/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers.py b/src/handlers.py index 5f68833..a454c67 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -1152,7 +1152,7 @@ async def handle_github_webhook(request, env): # Fetch fresh PR data webhook_token = getattr(env, 'GITHUB_TOKEN', None) fetched_pr_data = await fetch_pr_data(repo_owner, repo_name, pr_number, webhook_token) - if fetched_pr_data: + if fetched_pr_data and not fetched_pr_data.get('not_found') and not fetched_pr_data.get('not_modified'): await upsert_pr(db, pr_url, repo_owner, repo_name, pr_number, fetched_pr_data) # Invalidate caches await invalidate_readiness_cache(env, pr_id) @@ -1175,7 +1175,7 @@ async def handle_github_webhook(request, env): # Fetch fresh PR data webhook_token = getattr(env, 'GITHUB_TOKEN', None) fetched_pr_data = await fetch_pr_data(repo_owner, repo_name, pr_number, webhook_token) - if fetched_pr_data: + if fetched_pr_data and not fetched_pr_data.get('not_found') and not fetched_pr_data.get('not_modified'): await upsert_pr(db, pr_url, repo_owner, repo_name, pr_number, fetched_pr_data) # Invalidate caches to force fresh analysis await invalidate_readiness_cache(env, pr_id) From 66faafed4a01c1723999ab99a743927489a700f4 Mon Sep 17 00:00:00 2001 From: SshauryaaRocks19 <198586011+SshauryaaRocks19@users.noreply.github.com> Date: Sun, 15 Mar 2026 01:27:49 +0530 Subject: [PATCH 4/4] fix: Return a 500 error response when GitHub PR data fails to be fetched. --- src/handlers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/handlers.py b/src/handlers.py index a454c67..54d5a4f 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -1169,6 +1169,11 @@ async def handle_github_webhook(request, env): }), {'headers': {'Content-Type': 'application/json'}} ) + else: + return Response.new( + json.dumps({'error': 'Failed to fetch PR data from GitHub'}), + {'status': 500, 'headers': {'Content-Type': 'application/json'}} + ) # Handle synchronized (new commits) or edited PRs - update data elif action in ['synchronize', 'edited']: @@ -1192,6 +1197,11 @@ async def handle_github_webhook(request, env): }), {'headers': {'Content-Type': 'application/json'}} ) + else: + return Response.new( + json.dumps({'error': 'Failed to fetch PR data from GitHub'}), + {'status': 500, 'headers': {'Content-Type': 'application/json'}} + ) # Handle other event types - update PR data to refresh behind_by and mergeable_state elif event_type in ['pull_request_review', 'check_run', 'check_suite']: