Summary
allowed_bots bypasses the human actor check (checkHumanActor in actor.ts) but does not bypass the write permission check (checkWritePermissions in permissions.ts). When a bot like GitHub Copilot triggers a pull_request_review event, the action fails with a 404 from the collaborators API because bot accounts are not repository collaborators.
Reproduction
- Configure a workflow triggered by
pull_request_review with allowed_bots: "Copilot"
- Request a Copilot review on a PR
- Copilot submits its review, triggering the workflow with
actor: Copilot
Expected: The action runs successfully, with allowed_bots bypassing both the actor check and the permission check.
Actual: The action fails:
Checking permissions for actor: Copilot
GET /repos/{owner}/{repo}/collaborators/Copilot/permission - 404
##[error] Failed to check permissions: HttpError: Copilot is not a user
##[error] Action failed with error: Actor does not have write permissions to the repository
Root cause
In src/github/validation/permissions.ts, checkWritePermissions has three bypass paths:
allowedNonWriteUsers match — but this uses a separate input (allowed_non_write_users), not allowed_bots
actor.endsWith("[bot]") — but Copilot's login is Copilot, not copilot-pull-request-reviewer[bot]
- Collaborators API returns
admin or write — but bot accounts are not collaborators, so the API returns 404
None of these paths handle a bot that is listed in allowed_bots but doesn't have the [bot] suffix.
Meanwhile, checkHumanActor in actor.ts correctly handles allowed_bots — it fetches the user type via octokit.users.getByUsername, sees type: "Bot", and checks the allowed_bots list. But execution never reaches that point because checkWritePermissions throws first.
The call order in src/entrypoints/run.ts (line ~175):
// checkWritePermissions runs first and throws for bot actors
const hasWritePermissions = await checkWritePermissions(
octokit.rest, context, context.inputs.allowedNonWriteUsers, ...
);
// checkHumanActor runs later (in prepareAgentMode) and correctly checks allowed_bots
await checkHumanActor(octokit.rest, context);
Suggested fix
In checkWritePermissions, add an allowed_bots check that mirrors the logic in checkHumanActor. If the actor is listed in allowed_bots (or allowed_bots is *), bypass the permission check. This could be done by:
- Passing
allowedBots into checkWritePermissions
- Catching the 404 from the collaborators API and checking
allowed_bots before throwing
- Or moving the
allowed_bots check earlier in run.ts before the permission check
Context
- GitHub Copilot's review actor login is
Copilot (type: Bot, id: 175728472), not copilot-pull-request-reviewer[bot]
- This was discovered while building a Copilot review triage workflow that uses Claude to evaluate and respond to Copilot's review comments
- The
allowed_bots input description says it allows "bot usernames" to trigger the action, which implies it should handle the full lifecycle including permission checks
- Version tested: v1.0.77 (pinned SHA
ff9acae5886d41a99ed4ec14b7dc147d55834722)
Workaround
Re-dispatch the workflow via workflow_dispatch so the triggering actor becomes a human user instead of the bot. This works but adds complexity and latency.
Summary
allowed_botsbypasses the human actor check (checkHumanActorinactor.ts) but does not bypass the write permission check (checkWritePermissionsinpermissions.ts). When a bot like GitHub Copilot triggers apull_request_reviewevent, the action fails with a 404 from the collaborators API because bot accounts are not repository collaborators.Reproduction
pull_request_reviewwithallowed_bots: "Copilot"actor: CopilotExpected: The action runs successfully, with
allowed_botsbypassing both the actor check and the permission check.Actual: The action fails:
Root cause
In
src/github/validation/permissions.ts,checkWritePermissionshas three bypass paths:allowedNonWriteUsersmatch — but this uses a separate input (allowed_non_write_users), notallowed_botsactor.endsWith("[bot]")— but Copilot's login isCopilot, notcopilot-pull-request-reviewer[bot]adminorwrite— but bot accounts are not collaborators, so the API returns 404None of these paths handle a bot that is listed in
allowed_botsbut doesn't have the[bot]suffix.Meanwhile,
checkHumanActorinactor.tscorrectly handlesallowed_bots— it fetches the user type viaoctokit.users.getByUsername, seestype: "Bot", and checks theallowed_botslist. But execution never reaches that point becausecheckWritePermissionsthrows first.The call order in
src/entrypoints/run.ts(line ~175):Suggested fix
In
checkWritePermissions, add anallowed_botscheck that mirrors the logic incheckHumanActor. If the actor is listed inallowed_bots(orallowed_botsis*), bypass the permission check. This could be done by:allowedBotsintocheckWritePermissionsallowed_botsbefore throwingallowed_botscheck earlier inrun.tsbefore the permission checkContext
Copilot(type:Bot, id:175728472), notcopilot-pull-request-reviewer[bot]allowed_botsinput description says it allows "bot usernames" to trigger the action, which implies it should handle the full lifecycle including permission checksff9acae5886d41a99ed4ec14b7dc147d55834722)Workaround
Re-dispatch the workflow via
workflow_dispatchso the triggering actor becomes a human user instead of the bot. This works but adds complexity and latency.