The Copilot Issue Dispatcher is a GitHub Actions automation that delegates queued issues to the GitHub Copilot Coding Agent by posting a structured comment on each issue. Copilot then opens a pull request itself – the dispatcher never creates branches or PRs directly.
The system supports a zero-touch pipeline from issue creation to Copilot PR:
Issue created / label added
→ auto-queue-issues.yml adds `queue/copilot` (event-driven, instant)
→ copilot-dispatcher.yml delegates to Copilot (≤ 30 min or on label event)
→ Copilot Coding Agent opens a draft PR
→ pr-copilot-trigger.yml sends context to the agent
→ Agent finishes implementation and marks PR ready
→ copilot-readiness-gate.yml promotes PR to `copilot/status-ready`
| Workflow | Trigger | Role |
|---|---|---|
auto-queue-issues.yml |
issues: opened + workflow_dispatch |
Adds queue/copilot to new issues automatically; back-fill mode queues all existing open issues on demand |
copilot-dispatcher.yml |
Schedule (30 min) + issues: labeled |
Delegates queued issues to the Copilot Coding Agent |
pr-copilot-trigger.yml |
pull_request: opened/synchronize/reopened |
Posts CI context to the agent on draft Copilot PRs |
copilot-readiness-gate.yml |
pull_request: labeled + check_suite: completed |
Promotes Copilot PRs that pass all gates to copilot/status-ready |
- GitHub Copilot Coding Agent must be enabled for the repository or
organisation. Without it the
@copilotmention in the delegation comment will have no effect. - The workflow requires only
issues: writepermission (post comments, add/remove labels). Nocontents: writeorpull-requests: writepermissions are needed.
- The dispatcher runs on a schedule (every 30 min) or via manual trigger.
- It finds all open issues that have the label
queue/copilotbut NOTcopilot/delegatedand are notblocked. - For each qualifying issue (up to
max_delegations_per_run) it: a. Sorts the eligible issues by priority (critical → high → medium → low → unlabelled), then by creation date within the same priority tier. b. Checks the issue's comment thread for the idempotency marker<!-- copilot-delegated -->. If the marker is already present the issue is skipped (prevents double-delegation). c. Posts a delegation comment that mentions@copilotand includes:- A link to the issue.
- Acceptance criteria extracted from the issue body (task-list checkboxes) or a generated default criterion.
- Task notes: target branch, coding standards, test requirements,
Closes #<N>instruction. d. Assigns thecopilotuser to the issue to start the Copilot Coding Agent. The assignment is the authoritative trigger; a@copilotmention posted by a bot account alone is not sufficient to start the agent. e. Applies the labelcopilot/delegatedto the issue. f. Removes the labelqueue/copilotfrom the issue.
- Copilot picks up the assignment, implements the changes, and opens a PR that closes the issue.
<!-- copilot-delegated -->
@copilot Please implement the changes required to resolve this issue and open a pull request.
## Issue: [#<N> – <title>](<url>)
> <first non-heading line of the issue body, up to 300 characters>
## Requirements / Acceptance Criteria
- [ ] <checkbox items extracted from the issue body>
(falls back to "Resolve issue #<N>: <title>" when the issue body has no checkboxes)
## Task Notes
- Target the `<base_branch>` branch for your pull request.
- Follow the repository's contribution guidelines and code standards.
- Ensure all existing tests pass and add new tests where appropriate.
- Reference this issue in the PR body with `Closes #<N>`.
---
*This delegation was posted automatically by the Copilot Issue Dispatcher.*The HTML comment <!-- copilot-delegated --> acts as the idempotency marker –
the dispatcher will skip any issue whose comment thread already contains this string.
The dispatcher processes eligible issues in the following order within each run:
| Priority label | Aliases | Order |
|---|---|---|
priority:critical |
P0 |
1st – processed first |
priority:high |
P1 |
2nd |
priority:medium |
P2 |
3rd |
priority:low |
P3 |
4th |
| (no priority label) | — | Last |
Within the same priority tier issues are processed oldest-first (by creation date).
| Label | Purpose |
|---|---|
queue/copilot |
Mark an issue as eligible for Copilot delegation. Added automatically by auto-queue-issues.yml when a new issue is opened, or manually. Removed by the dispatcher after posting the delegation comment. |
copilot/delegated |
Set by the dispatcher after the delegation comment is posted. Prevents re-delegation on subsequent runs. |
blocked / status:blocked |
Issues with this label are skipped by both the auto-queue workflow and the dispatcher. |
The Auto-Queue Issues workflow (auto-queue-issues.yml) makes the entire
pipeline hands-free:
- New issues: Every issue that is opened automatically receives the
queue/copilotlabel (unless it is alreadyblocked,status:blocked, orcopilot/delegated). No manual step is required. - Existing/back-fill: Trigger the workflow manually via
Actions → Auto-Queue Issues for Copilot → Run workflow to add
queue/copilotto all currently open issues that are not yet queued. A dry-run option lets you preview which issues would be queued before writing any labels. A max_issues cap prevents accidental bulk-queuing.
The dispatcher then picks up every queued issue within ≤ 30 minutes.
- Open or find an existing issue.
- Add the label
queue/copilotmanually. - The dispatcher will pick it up within ≤ 30 minutes (or immediately on the next manual trigger).
Edit .github/copilot-dispatcher.yml:
# Maximum issues delegated per dispatcher run.
max_delegations_per_run: 5
# Base branch Copilot should target when opening PRs.
base_branch: developWhen triggering the workflow manually via Actions → Copilot Issue Dispatcher →
Run workflow, you can override the per-run limit with the max_delegations
input (leave at 0 to use the configured default).
The dispatcher is safe to re-run at any time:
- Issues that already have
copilot/delegatedare excluded by the GitHub search query. - As an additional guard, the dispatcher reads the issue's comments and skips
any issue whose thread already contains the delegation marker
<!-- copilot-delegated -->. This covers the edge case where the comment was posted but the label update failed in a previous run.
| Operation | Failure behaviour |
|---|---|
| Fetch queued issues | Abort the run |
| Read comments (idempotency check) | Warning + skip the issue |
| Post delegation comment | Warning + skip the issue |
Assign copilot to the issue |
Warning only (comment already posted) |
Apply copilot/delegated label |
Warning only (comment already posted) |
Remove queue/copilot label |
Warning only (404 = already absent, ignored) |
Critical failures (e.g., cannot query issues) abort the entire run immediately
with core.setFailed() and emit a budget hint so operators know a re-run costs
Actions minutes.
Run the label governance workflow once to ensure all required labels exist:
Actions → Label Governance – Setup & Audit → Run workflow → action: create
Or create the required labels manually:
| Label | Kind | Purpose |
|---|---|---|
queue/copilot |
Issue | Mark issue as eligible for Copilot delegation |
copilot/delegated |
Issue | Delegation comment posted; Copilot has been tasked |
blocked |
Issue | Skip this issue in the dispatcher |
- The dispatcher does not create branches, push commits, or open PRs. All of that is handled by the GitHub Copilot Coding Agent after receiving the issue assignment.
- GitHub Copilot Coding Agent must be enabled on the repository or
organisation. The dispatcher triggers the agent by assigning the
copilotuser to the issue; a@copilotmention in a bot-posted comment alone is not sufficient. - Auto-merge is not implemented. Human review and merge are fully independent of the dispatcher.
Writing issues that the dispatcher handles well improves the quality of Copilot-generated pull requests.
The dispatcher extracts acceptance criteria from the issue body by scanning
for Markdown task-list items (- [ ] …). When found, each item becomes a
checkbox in the delegation comment that Copilot uses as its work checklist.
Good issue body excerpt:
## Acceptance Criteria
- [ ] Add unit tests for the new function covering edge cases.
- [ ] Update the public API documentation in `docs/api.md`.
- [ ] Ensure no regressions in the existing test suite.When no task-list items are found the dispatcher falls back to a generic
- [ ] Resolve issue #<N>: <title> line. Issues with explicit checkboxes
consistently produce more focused, correct pull requests.
The dispatcher extracts the first non-heading, non-empty line of the issue body (up to 300 characters) as a context excerpt for Copilot. Front-load the most important context:
## Summary
Implement a connection-pool timeout so idle connections are evicted after
30 s (configurable) to prevent resource exhaustion under low-traffic loads.| Label | Why it matters |
|---|---|
queue/copilot |
Queues the issue for dispatcher pick-up. |
priority:critical / priority:high |
Processed first; use for blockers. |
blocked / status:blocked |
Excludes the issue from dispatcher runs. |
Copilot works best on issues that map to a single, bounded code change.
Split large epics into child issues before adding queue/copilot.
- Copilot Coding Agent not enabled – Visit Repository Settings → Copilot → Coding agent and confirm it is enabled. Without this, no assignment will start an agent session.
- Dispatcher did not run – Go to Actions → Copilot Issue Dispatcher and check for recent runs. The schedule fires every 30 minutes; you can also trigger a run manually via Run workflow.
- Issue not picked up – Open the run's job summary and look at the
"Queued issues available" log line. If the count is 0, verify the issue
actually has the
queue/copilotlabel and is open (GitHub's search index can lag by up to 60 seconds after a label is added; the next scheduled run will catch it). - Delegation limit reached – The
max_delegations_per_runconfig cap (default 2) may have been exhausted by other issues in the same run. Wait for the next run, increase the cap, or trigger a manual run. copilotwas not assigned – The dispatcher logs a warning if theaddAssigneescall fails. Check the run log for lines like "Could not assign 'copilot'". The most common cause is thatissues: writepermission was removed or the token was revoked.- Branch protection blocking Copilot – If the
developbranch requires status checks or reviews that the agent cannot satisfy, the agent's PR creation may fail silently. Review branch protection rules at Repository Settings → Branches and consider adding a bypass for the Copilot app.
The delegation comment is informational; the assignment of the copilot
user is what starts the agent session. Confirm both are present on the issue:
- A comment beginning with
<!-- copilot-delegated -->. - The
copilotuser listed under Assignees.
If the comment exists but the assignee is missing, trigger a manual dispatcher
run; the idempotency path will re-try the addAssignees call.
This should not happen under normal operation (the idempotency marker prevents it), but if it does the extra comments are harmless – only the first assignment triggers the agent. Remove duplicate comments manually and consider opening a bug report.
Navigate to the issue page and look for the Copilot activity section, or visit:
Repository → Pull requests and filter by the branch Copilot creates
(format: copilot/<issue-number>-<slug>).
See also: Tracking Copilot sessions
- GitHub Copilot coding agent overview
- Troubleshooting the Copilot coding agent
- Tracking Copilot sessions
- GitHub Actions permissions for
GITHUB_TOKEN
After the Copilot Coding Agent opens a draft PR in response to a delegated
issue, the companion workflow pr-copilot-trigger.yml ensures the agent
receives a follow-up signal so it can continue the implementation.
- The workflow fires on every
pull_requestevent (opened,synchronize,reopened). - It passes two gates before posting a comment:
- Gate 1 – Draft only: the PR must be in draft state.
- Gate 2 – Label check: the PR must carry the label
pr/copilotoragent:copilot-task.
- Idempotency: on
opened/reopenedevents the workflow checks whether a comment with the marker<!-- copilot-pr-trigger -->already exists. If it does, no duplicate comment is posted. Onsynchronizeevents (new commits pushed) a fresh comment is always posted so the agent sees the latest CI context. - The trigger comment is built from:
- PR number, title, URL, branch info.
- An excerpt of the PR body (first 300 characters, headings stripped).
- CI scope – which areas of the codebase were changed, as reported by
the
ci-scope-classifierreusable workflow. - CI status – current pass / fail / pending counts from GitHub check runs on the PR head SHA.
- A Next Steps section instructing the agent to finish the implementation and convert the PR to ready-for-review when done.
<!-- copilot-pr-trigger -->
@copilot This draft PR was created for you. Please continue the implementation.
## PR Context
| Field | Value |
|---|---|
| **PR** | [#<N> – <title>](<url>) |
| **Branch** | `<head>` → `<base>` |
| **Event** | `opened` |
| **Draft** | yes |
> <PR body excerpt>
### CI Scope (changed areas)
- <list of changed areas from ci-scope-classifier>
### CI Status
- ✅ Passed/skipped: N
- ⏳ Pending: N (check-name, ...)
- ❌ Failed: N (check-name, ...)
## Next Steps
- Implement all items listed in the PR description / linked issue.
- Ensure all existing tests pass and add new tests where appropriate.
- Follow the repository's contribution guidelines and code standards.
- When implementation is complete, mark the PR ready for review
(convert from draft) and request a review.
---
*This comment was posted automatically by the PR Copilot Trigger workflow.*The workflow checks for these labels on the PR:
| Label | Purpose |
|---|---|
pr/copilot |
Marks a PR as Copilot-owned; triggers the workflow |
agent:copilot-task |
Alternative Copilot-task label; also triggers the workflow |
Once the agent has finished and converts the PR from draft to ready-for-review,
the copilot-readiness-gate.yml workflow takes over: it verifies that all
CI checks pass and a Copilot review has been approved, then promotes the PR with
the copilot/status-ready label.