Decision. Pick by where the trigger lives.
- External system (webhook, cron job, other repo) → fire
repository_dispatchand letpeter-evans/create-pull-requestdo the branch+PR work [1] [7].- Inside GitHub (issue, label, comment) → an
issuesorissue_commentworkflow, optionally fan-out viaslash-command-dispatch[9].- Chat / ticket (Slack, Linear, Jira) → hand off to a hosted agent (Copilot coding agent, Devin, Cursor, Codex Cloud) that owns the branch+PR step itself [13] [17] [20].
All three hit the same gotcha: a PR opened with the default
GITHUB_TOKENwill not trigger downstreampush/pull_requestworkflows — you need a PAT or a GitHub App token if you want CI to run on the bot’s PRs [6] [8].
The trigger primitive
GitHub Actions exposes two remote-trigger events; everything else is a GitHub-native event you can listen for [1].
| Trigger | Where it fires from | Branch the workflow runs from | Inputs | Notes |
|---|---|---|---|---|
workflow_dispatch |
UI button, gh workflow run, POST /actions/workflows/{id}/dispatches |
Caller chooses (--ref / ref body field) |
Up to 10 typed inputs (string, boolean, choice, environment) |
The “manual button with parameters” trigger [2] [3] |
repository_dispatch |
POST /repos/:owner/:repo/dispatches with event_type + client_payload |
⚠ Default branch only — ref/branch is not selectable by the caller |
Free-form client_payload JSON |
The “external webhook” trigger; workflow file must be on default branch [1] [5] |
issues / issue_comment |
Activity inside GitHub | Default branch | Event payload (issue body, comment, labels) | Foundation for label-triggered, @mention, and slash-command flows [10] |
schedule |
Cron | Default branch | None | Useful when “remote” is “time of day” |
The split matters. workflow_dispatch is the right choice when you control the caller and want a chosen branch + structured inputs (your own bot, an internal dashboard, a gh script). repository_dispatch is the right choice when something genuinely external posts a webhook — but the workflow lives on the default branch, period [5]. peter-evans/repository-dispatch ⭐ 1.2k (Apr 2026) is the standard wrapper for emitting one from inside another workflow [4].
The branch + PR step
Once the workflow is running, three options open or update a PR. The first is overwhelmingly the standard.
| Tool | Mechanism | When to pick it |
|---|---|---|
peter-evans/create-pull-request ⭐ 2.8k |
Detects workspace changes, commits to a new branch, opens or updates a PR via REST | Default choice — handles update-existing-PR semantics, draft mode, signed commits, labels [7] |
gh pr create |
One-line CLI | Single-commit, single-PR scripts where you don’t need update-or-create logic |
actions/github-script |
JS calling Octokit | When you need bespoke API calls (e.g. cross-repo PR, complex labels, comment-on-PR) |
The action is on v8 (Apr 2026); it requires the repo-level toggle Settings → Actions → General → Workflow permissions → Allow GitHub Actions to create and approve pull requests to be on, otherwise it fails with a 403 [7].
The token gotcha
This bites everyone exactly once: a PR pushed by GITHUB_TOKEN will not start push or pull_request workflows on the new branch [6]. GitHub does this on purpose to prevent recursive runs. The result: your bot opens a PR, but CI never runs on it, and merge gates stay grey.
Three fixes, in increasing order of cleanliness [8]:
| Fix | Trade-off |
|---|---|
Classic PAT with repo + workflow scopes |
Easiest. Tied to one user; expires; broad scope |
Fine-grained PAT (contents: write, pull-requests: write) |
Scoped to specific repos; still per-user |
| GitHub App with installation token | ✓ Per-repo install, audit-friendly, no expiry rotation pain. The recommended path |
Issue → PR via comments and labels
The issue_comment event is how almost every “@mention me to fix this” workflow is built. Naive shape: one workflow listens for issue_comment and does the work inline. Better shape: a thin handler matches the slash command and re-emits a repository_dispatch, so the comment-event queue stays fast and the heavy work runs in parallel [10]. peter-evans/slash-command-dispatch ⭐ 685 (Apr 2026) packages exactly that pattern [9].
Label-triggered shape is the same idea with issues: types: [labeled] and a conditional on github.event.label.name. Common in AI-assisted flows — Aider’s reference workflow tags an issue with aider, the action then branches and PRs against it [22].
Hosted bots: dependency updates
For dependency PRs the trigger is “the dependency moved”, not a human action. Two tools dominate.
| Dependabot | Renovate ⭐ 21k | |
|---|---|---|
| Trigger | Schedule + GitHub Security Advisories | Configurable schedule per dep/manager [24] |
| Platforms | GitHub only (official) | GitHub, GitLab, Bitbucket, Azure DevOps, Forgejo, Gitea [23] |
| Monorepo grouping | ✗ One PR per dep unless groups: configured |
✓ Built-in preset groups monorepo packages into one PR [24] |
| Setup | Built into GitHub, zero install | App install or self-host |
| Best fit | Single-repo GitHub project, want zero-config | Monorepos, multi-platform, fine-grained policy |
Hosted AI agents: ticket → PR
These compress the whole pipeline (trigger → branch → edit → push → PR) into a single product. The differences are mostly where the trigger lives and who hosts the runtime.
| Agent | Trigger surface | Runtime | Cited |
|---|---|---|---|
| Copilot coding agent (GitHub) | Assign issue to Copilot; @copilot in PR; agents panel |
Ephemeral GitHub Actions environment in your repo | [13] [14] |
| Claude Code action ⭐ 7.3k | @claude in issue/PR/comment, or issue assignment |
Your GitHub Actions runner | [11] [12] |
| OpenAI Codex Cloud ⭐ 78k | @codex on issue/PR; ChatGPT web app; Codex CLI |
OpenAI-hosted sandbox | [15] [16] |
| Devin | Slack mention, Linear/Jira ticket, web app | Cognition-hosted | [17] [18] |
| Cursor background agents | @cursor in PR/issue; Slack message; new Linear issue; merged GitHub PR; PagerDuty incident |
Cursor-hosted | [19] [20] |
| Aider + GitHub Action ⭐ 44k | Label aider on an issue |
Your GitHub Actions runner | [21] [22] |
Two axes to weigh:
- Runs in your CI vs hosted. Claude Code action and Aider run on your own GitHub Actions runner — your secrets, your VPC, your bill. Copilot agent runs in GitHub-hosted ephemeral Actions still scoped to your repo. Devin, Cursor, Codex Cloud are external SaaS that clone your repo, do the work, and push back.
- Trigger reach. If the trigger source is GitHub (issue, comment, label), every option works. If it’s Slack/Linear/Jira/PagerDuty, only Devin and Cursor have first-party integrations [17] [20]; the rest need you to bridge with a webhook →
repository_dispatch[1].
GitLab equivalents (one paragraph)
Same shape, different names. GitLab’s repository_dispatch is the trigger token: POST /projects/:id/trigger/pipeline with a token and ref starts a pipeline on any branch [25] — strictly more flexible than repository_dispatch since ref is selectable. The PR step is POST /projects/:id/merge_requests against the Merge Requests API [26]. Renovate covers GitLab/Bitbucket/Forgejo/Gitea natively [23].
A concrete example: Scout
This very repo uses two of the patterns. Open an issue tagged scout-research → issues: [opened] workflow runs sharpening and posts a checkbox-driven proposal. Tick the Start research checkbox → issue_comment: [edited] workflow on github.event.changes.body.from mutation runs the research and pushes to a sibling Atlas repo (separate SSH host alias). For headless invocations, the same workflow exposes workflow_dispatch with topic/depth/format inputs, so a cron job or external script can fire gh workflow run research.yml -f topic=…. No PR is ever opened on the source repo — the artifact lives in a downstream site repo — but the trigger fan-out (UI / issue / comment / external API) is exactly the structure described above.