Decision. For a Slack/chat → PR pipeline, fire a
repository_dispatchfrom your bot, run the work on a GitHub-hosted (or self-hosted) runner, and letanthropics/claude-code-action[6] ⭐ 7.3k orpeter-evans/create-pull-request[1] ⭐ 2.8k push the branch and open the PR. Auth via a custom GitHub App token, not the defaultGITHUB_TOKEN— otherwise downstream CI on the PR will not run [2]. For non-AI workflows (issue → scaffold branch, dependency bump), the same shape works withgh pr createorpeter-evans/create-pull-requestdirectly.
The four moving parts
Any “remote thing happens → branch + PR” pipeline factors into four decisions. Pick once per layer; everything else follows.
| Layer | Question | Common answers |
|---|---|---|
| Transport | How does the external event reach GitHub? | repository_dispatch API call · workflow_dispatch · GitHub App webhook · scheduled cron · label/comment event |
| Compute | Where does the branch+PR work execute? | GitHub-hosted runner · self-hosted runner · external server (Probot/Lambda/your bot) |
| Auth | What token signs the push and PR? | GITHUB_TOKEN · classic PAT · fine-grained PAT · GitHub App installation token |
| PR mechanics | What actually creates the branch and PR? | peter-evans/create-pull-request · gh pr create · Octokit (octokit-plugin-create-pull-request) · the Claude/Copilot coding agent itself |
Transport: how the trigger gets in
| Transport | Best for | Branch it runs on | Payload limit | Notes |
|---|---|---|---|---|
repository_dispatch API |
External services (Slack bot, Linear, your own webhook receiver) firing into a repo | Default branch only [3] | 65,535 chars / 10 top-level props in client_payload [3] |
Since Sep 2022, GITHUB_TOKEN can fire it from inside another workflow [4] |
workflow_dispatch |
Operator hits “Run workflow” or a script targets a specific workflow | Any ref you specify [3] | 25 typed inputs, 65,535 chars total [3] | Inputs are typed (string/choice/boolean); repository_dispatch is opaque JSON |
| GitHub App webhook → external server | High-volume, multi-repo bots (Renovate, Probot apps) | N/A — the bot drives git push and the PR API itself |
Full webhook payload | Bot manages its own auth and rate limits |
Schedule (cron) |
Periodic scans (dependency bumps, dead-link sweeps) | Default branch | n/a | Renovate’s primary mode [11] ⭐ 21k |
| Issue / label / comment event | In-GitHub IssueOps — /cib, @claude implement …, label ai-scaffold |
The repo’s branches | n/a | The native IssueOps pattern [8] |
peter-evans/repository-dispatch [13] ⭐ 1.2k is the de-facto wrapper for firing a dispatch from inside one workflow into another.
Compute: where the branch+PR work runs
| Surface | When to pick it | Tradeoffs |
|---|---|---|
| GitHub-hosted runner | Default. Stateless, ephemeral, free quota for public repos. | 6 h job timeout; no access to your private network. |
| Self-hosted runner | Work needs your VPC, internal package registries, or longer wall-clock than 6 h. Copilot coding agent gained this in Oct 2025 [17]. | ⚠ Public-repo PRs from forks can run code on your runner — keep these on GitHub-hosted only. |
| External server (Probot, Lambda, your own bot) | Long-lived bots like Renovate, or when the trigger is not a GitHub event at all. | You own auth, retries, rate-limit handling. Probot [9] ⭐ 9.5k handles the webhook-event-emitter shape; Octokit handles the Tree/Commit API for actual pushes [18] ⭐ 114. |
Auth: the one footgun everyone hits
The default GITHUB_TOKEN looks fine until you notice CI never runs on the PRs your automation opens. From peter-evans’s own concepts doc [2]:
Pull requests created with the default token “cannot trigger other workflows.”
Documented in community threads going back years [5]. Pick one of these instead:
| Token | Triggers downstream workflows? | Scoping | Recommended for |
|---|---|---|---|
GITHUB_TOKEN (default) |
✗ on PR creation; ✓ for repository_dispatch/workflow_dispatch since 2022 [4] |
Auto-scoped to current repo | Trigger fan-out only; never for the PR push itself |
| Classic PAT | ✓ | Coarse — repo or org-wide | Quick PoC; the user account becomes the PR author |
| Fine-grained PAT | ✓ | Per-repo, scoped permissions | One-off bots where a GitHub App is overkill |
GitHub App installation token (via actions/create-github-app-token [14] ⭐ 796) |
✓ | Per-repo, per-permission, ephemeral (1 h) | Default choice for production. Branded bot identity, rotates automatically. |
Anthropic’s docs make the same call: their troubleshooting section literally lists “Ensure you’re using the GitHub App or custom app (not Actions user)” as the fix when CI does not run on Claude’s commits [7].
PR mechanics: what pushes the branch
| Tool | Shape | Stars / source | Best fit |
|---|---|---|---|
peter-evans/create-pull-request |
Action step. Diffs the workspace, commits to a branch, opens/updates a PR. v8.1.1 (Apr 2026), needs contents: write + pull-requests: write [1] |
⭐ 2.8k | The default for “I have an Actions workflow that mutates files; turn that into a PR.” |
gh pr create |
CLI step. You drive git checkout -b / git push yourself, then gh pr create --fill [15]. |
official | Scripts that already use git directly; non-interactive automation. |
anthropics/claude-code-action@v1 |
Agentic — Claude itself decides on edits, runs git, opens the PR via the action’s plumbing [7]. |
⭐ 7.3k [6] | Issue-to-PR with @claude trigger, or any prompt-driven flow. |
octokit-plugin-create-pull-request |
Node library. Builds the tree/commits via the GitHub API — no git binary needed [18]. |
⭐ 114 | Probot apps and serverless webhook receivers, where shelling out to git is awkward. |
robvanderleek/create-issue-branch |
Probot app + Action. Creates issue-15-fix_nasty_bug branch on assign or /cib comment; can also open the PR on label [10]. |
⭐ 346 | The “scaffold a branch from an issue” half of the flow, without writing it yourself. |
| Renovate | Long-running external bot that scans on a schedule and opens PRs across GitHub/GitLab/Bitbucket/Azure DevOps [11]. | ⭐ 21k | Reference architecture for “many repos, many PRs, no in-repo workflow per repo.” |
| Copilot coding agent | Asynchronous background agent that opens a draft PR, edits in the background, then requests review [17]. | official | When the trigger is a GitHub Issue assigned to Copilot and you want a managed agent surface. |
End-to-end shapes worth copying
Shape A — Slack message → PR (chat-driven coding)
- Slack slash-command (or thread reaction) hits your bot.
- Bot fires
POST /repos/{owner}/{repo}/dispatcheswithevent_type: "slack-claude"andclient_payload: { user, thread_ts, prompt, channel }. - A workflow
on: repository_dispatch: types: [slack-claude]runsactions/create-github-app-token, thenanthropics/claude-code-action@v1withprompt: $. - Claude edits files, the action commits, pushes a
claude/<thread-ts>branch, opens the PR. Because the push is signed with a GitHub App token, downstream CI runs [2]. - Bot watches
pull_request.opened(or pollsgh pr view) and posts the PR URL back to the Slack thread.
Kilo for Slack [16] is the productized version of exactly this loop.
Shape B — Issue label → scaffold branch + draft PR
- User opens an issue, adds label
ai-scaffold. - Workflow
on: issues: types: [labeled]withif: github.event.label.name == 'ai-scaffold'. robvanderleek/create-issue-branch[10] createsissue-{N}-{slug}branch and (configured) opens a draft PR.- Optional: chain a
claude-code-actionstep onpull_request.openedto populate the branch with a first-pass implementation.
This is the IssueOps pattern [8] — the audit trail (label, branch, PR, comments) all lives inside GitHub.
Shape C — Scheduled scan → many PRs
- Renovate (or a custom workflow on
schedule:) scans the repo on cron. - Each finding gets its own branch and PR, opened against the configured base branch [11].
- Use
peter-evans/create-pull-requestwithbranch-suffix: random[1] to avoid colliding branches when several findings open in parallel.
Pitfalls
pull_request_target+ checkout of fork code is a known repo-takeover pattern [12]. If your branch+PR automation must run on contributions from forks, treat the fork’s HEAD as untrusted: don’t check it out into a job that has write tokens, and split the job into a sandboxed read-only stage and a privileged write-stage that operates only on data you’ve validated.repository_dispatchonly runs workflows on the default branch [3] — committing your dispatch handler on a feature branch and watching nothing fire is the most common mis-step.- The “create or update” PR branch needs to exist on a remote —
peter-evans/create-pull-requestwill not work for events that check out a commit (e.g.release,pull_request) without an explicitbaseinput [2]. - Self-hosted runners on public repos are dangerous because PRs from forks can execute on your hardware [17]. Keep automation that requires self-hosted runners in private repos, or gate it behind label-based triggers that maintainers add manually.
Recommendation matrix
| If the trigger is… | …and the work is… | Pick |
|---|---|---|
| Slack thread / external chat | LLM-driven code edits | repository_dispatch → claude-code-action@v1 [6] ⭐ 7.3k |
GitHub Issue with @claude mention |
LLM-driven code edits | Native claude-code-action issue_comment trigger [7] |
| GitHub Issue assignment / label | Deterministic scaffolding | create-issue-branch [10] ⭐ 346 |
| Cron / schedule | Dependency or content sweep | Renovate [11] ⭐ 21k or workflow + peter-evans/create-pull-request [1] ⭐ 2.8k |
| Webhook from non-GitHub source (Linear, Sentry) | Custom logic | Probot app [9] ⭐ 9.5k + octokit-plugin-create-pull-request [18] ⭐ 114 |
| Anything | Anything in production | Mint a GitHub App installation token via actions/create-github-app-token [14] ⭐ 796 — never the default GITHUB_TOKEN for the actual PR push [2] |