Skip to content

feat: add webhook notification on merge completion#25

Merged
vitali87 merged 2 commits intomainfrom
feat/merge-notifications
Mar 24, 2026
Merged

feat: add webhook notification on merge completion#25
vitali87 merged 2 commits intomainfrom
feat/merge-notifications

Conversation

@vitali87
Copy link
Copy Markdown
Owner

Summary

  • Add --notify <url> flag to the merge command (also reads PR_SPLIT_WEBHOOK_URL env var)
  • POSTs a JSON payload to the webhook URL after merge completes (success or failure)
  • Payload includes: event type, merged/skipped/failed lists, success boolean
  • Webhook failures are logged as warnings, never block the merge flow
  • Uses stdlib urllib.request — no new dependencies

Example payload:

{
  "event": "merge_complete",
  "merged": ["pr-1", "pr-2"],
  "skipped": ["pr-3 (draft)"],
  "failed": [],
  "success": true
}

Test plan

  • All 293 tests pass
  • Manual: run merge --notify https://httpbin.org/post and verify payload
  • Manual: run merge without --notify — no webhook sent

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new feature that enhances the merge command by adding webhook notification capabilities. This allows external systems to be automatically informed about the outcome of merge operations, providing better integration and automation possibilities without affecting the core merge process or adding new external dependencies.

Highlights

  • New --notify flag: A new --notify <url> flag has been added to the merge command, allowing users to specify a webhook URL. This URL can also be provided via the PR_SPLIT_WEBHOOK_URL environment variable.
  • Webhook Notification: After a merge operation completes (whether successful or failed), a JSON payload is POSTed to the configured webhook URL. This payload includes details such as the event type, lists of merged, skipped, and failed PRs, and a boolean indicating overall success.
  • Error Handling: Failures in sending webhook notifications are now gracefully handled by being logged as warnings, ensuring that they do not interrupt or block the primary merge workflow.
  • Dependency Management: The webhook functionality leverages Python's standard library urllib.request module, meaning no new external dependencies are introduced with this feature.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a webhook notification feature for merge completions, which is a great addition for CI/CD integration. The implementation is clean and uses Python's standard library. My review includes two main suggestions for improvement: refining the exception handling in the new webhook function to be more specific, and executing the webhook call in a background thread to prevent blocking the main process and improve the command-line user experience. These changes will make the new feature more robust and performant.

Comment on lines +601 to +602
except Exception as exc:
logger.warning(f"Failed to send webhook notification: {exc}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching a broad Exception is generally discouraged as it can mask unexpected errors and make debugging harder. It's better to catch more specific exceptions that you anticipate from the operations in the try block. For this block, OSError (which covers network errors like URLError and HTTPError), ValueError (for URL issues), and TypeError (for JSON serialization) would be more appropriate.

Suggested change
except Exception as exc:
logger.warning(f"Failed to send webhook notification: {exc}")
except (OSError, ValueError, TypeError) as exc:
logger.warning(f"Failed to send webhook notification: {exc}")

Comment on lines +722 to +729
if notify:
_send_webhook(notify, {
"event": "merge_complete",
"merged": merged,
"skipped": skipped,
"failed": failed,
"success": not (failed or stopped or exited_early),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Sending the webhook is a blocking network call that can delay the command's completion by up to 10 seconds. To provide a better user experience, it's best to send this notification in a background thread. You can use the already imported ThreadPoolExecutor to achieve this in a non-blocking way.

Suggested change
if notify:
_send_webhook(notify, {
"event": "merge_complete",
"merged": merged,
"skipped": skipped,
"failed": failed,
"success": not (failed or stopped or exited_early),
})
if notify:
executor = ThreadPoolExecutor(max_workers=1)
executor.submit(_send_webhook, notify, {
"event": "merge_complete",
"merged": merged,
"skipped": skipped,
"failed": failed,
"success": not (failed or stopped or exited_early),
})
executor.shutdown(wait=False)

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR adds a --notify <url> option (also read from PR_SPLIT_WEBHOOK_URL) to the merge command that POSTs a structured JSON payload to a webhook URL after the merge run completes, using only stdlib urllib.request. All four issues raised in the previous review round have been addressed: the HTTP response is now consumed and closed via a context manager, notify defaults to None instead of an empty-string sentinel, an exit_reason field disambiguates a "success": false result with an empty failed list, and skipped is now a list of structured objects rather than plain human-readable strings.

Two minor remaining items:

  • The reason field in each skipped object still contains the full formatted string (e.g. "pr-3 (draft)") rather than just the annotation ("draft"), making the separate id field redundant for consumers.
  • _send_webhook catches the broad Exception class, which may silently swallow unexpected programming errors; narrowing to urllib.error.URLError / OSError would be more precise.

Confidence Score: 4/5

  • Safe to merge; the two remaining issues are style-level and do not affect correctness of the merge flow.
  • The core merge logic is untouched. The new webhook path is purely additive, failures are swallowed as warnings and never block the merge, and the previous round of critical feedback has been fully resolved. The two open items (redundant reason field and overly broad exception catch) are minor and not blocking.
  • No files require special attention — all changes are confined to pr_split/cli.py.

Important Files Changed

Filename Overview
pr_split/cli.py Adds _send_webhook helper and --notify / PR_SPLIT_WEBHOOK_URL support to merge_all. Previous review concerns (response not closed, empty-string sentinel, ambiguous success: false, inconsistent skipped-list types) are all addressed. Two minor style issues remain: the reason field in skipped_structured duplicates the group ID, and the bare except Exception in _send_webhook is broader than necessary.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as merge_all (cli.py)
    participant GH as GitHub API
    participant WH as Webhook URL

    User->>CLI: pr-split merge [--notify URL]
    CLI->>GH: get_pr_state() for each PR
    GH-->>CLI: PR states

    loop For each batch in DAG
        CLI->>GH: merge_pr() or enable auto-merge
        alt --auto mode
            CLI->>GH: _poll_for_merged() (polls every 10s, 600s timeout)
            GH-->>CLI: MERGED / CLOSED / timeout
        end
    end

    CLI->>CLI: Build payload (merged, skipped_structured, failed, exit_reason)

    alt notify URL provided
        CLI->>WH: POST /webhook (JSON payload, timeout=10s)
        WH-->>CLI: HTTP response (read & closed)
        alt HTTP/network error
            CLI->>CLI: logger.warning (merge flow unaffected)
        end
    end

    CLI->>User: Exit 0 (success) or Exit 1 (failure/incomplete)
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: pr_split/cli.py
Line: 816-819

Comment:
**`reason` field duplicates the `id`**

The `reason` value is set to `s` — the full formatted string (e.g. `"pr-3 (draft)"`) — so it repeats the group ID that is already present in the `id` field. A consumer expecting `reason` to contain only the human-readable annotation (e.g. `"draft"`, `"fetch error"`, `"no PR"`) will instead have to strip the ID prefix out again, which defeats the purpose of splitting the two fields.

```suggestion
        skipped_structured = [
            {
                "id": s.split(" (")[0],
                "reason": s.split(" (")[1].rstrip(")") if " (" in s else s,
            }
            for s in skipped
        ]
```

This produces `{"id": "pr-3", "reason": "draft"}` — clean, non-redundant, and consistent with the structured format the previous review suggested.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: pr_split/cli.py
Line: 671-681

Comment:
**Broad `except Exception` swallows unexpected errors silently**

Catching `Exception` catches everything from `ValueError` (bad URL) to `MemoryError`, masking programming mistakes as mere log warnings. Consider narrowing to the specific error classes that `urlopen` can raise for network/HTTP failures, and let truly unexpected exceptions propagate:

```suggestion
def _send_webhook(url: str, payload: dict[str, object]) -> None:
    try:
        data = json_mod.dumps(payload).encode("utf-8")
        req = urllib.request.Request(
            url, data=data, headers={"Content-Type": "application/json"}
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            resp.read()
        logger.info(f"Webhook notification sent to {url}")
    except (urllib.error.URLError, OSError) as exc:
        logger.warning(f"Failed to send webhook notification: {exc}")
```

`urllib.error.URLError` (and its subclass `urllib.error.HTTPError`) covers all network-level and HTTP-level failures. `OSError` catches socket-level errors. This is specific enough to not silence real bugs.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (3): Last reviewed commit: "fix: add exit_reason to webhook payload ..." | Re-trigger Greptile

@vitali87 vitali87 force-pushed the feat/merge-notifications branch from b0c80f3 to a79180c Compare March 24, 2026 00:59
@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

@vitali87
Copy link
Copy Markdown
Owner Author

@greptile

@vitali87 vitali87 merged commit deac837 into main Mar 24, 2026
4 checks passed
@vitali87 vitali87 deleted the feat/merge-notifications branch March 27, 2026 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant