Skip to content

allowed_bots bypasses actor check but not permission check — bot actors still fail with 404 #1133

@bhill-godaddy

Description

@bhill-godaddy

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

  1. Configure a workflow triggered by pull_request_review with allowed_bots: "Copilot"
  2. Request a Copilot review on a PR
  3. 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:

  1. allowedNonWriteUsers match — but this uses a separate input (allowed_non_write_users), not allowed_bots
  2. actor.endsWith("[bot]") — but Copilot's login is Copilot, not copilot-pull-request-reviewer[bot]
  3. 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:

  1. Passing allowedBots into checkWritePermissions
  2. Catching the 404 from the collaborators API and checking allowed_bots before throwing
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:permissionsbugSomething isn't workingp2Non-showstopper bug or popular feature request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions