How to Restrict Who Can Trigger workflow_dispatch in GitHub Actions
Anyone with write access can trigger a manual workflow; for sensitive ones you may want an even tighter check.
Add a guard step that queries the actor permission level and fails the run unless they are an admin or maintainer.
Steps
- Keep the workflow on
workflow_dispatch. - Add a first job that checks the actor permission via the GitHub API.
- Fail the run if the level is below
admin(or your threshold). - Gate the real jobs behind that check with
needs:.
Workflow
on: workflow_dispatch
jobs:
authorize:
runs-on: ubuntu-latest
steps:
- name: Check permission
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LEVEL=$(gh api "repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission" --jq .permission)
if [ "$LEVEL" != "admin" ]; then
echo "::error::${{ github.actor }} is not an admin ($LEVEL)"
exit 1
fi
deploy:
needs: authorize
runs-on: ubuntu-latest
steps:
- run: ./deploy.shGotchas
- For broad org policy, a protected environment with required reviewers is sturdier than an inline check.
- The default token can read collaborator permission without extra scopes.
- Latchkey runs the gated jobs on cheaper, self-healing runners once the authorization check passes.
Verify it actually works
A workflow that runs is not a workflow that works. Confirm the behaviour on a real event rather than on a manual dispatch, because trigger conditions, permissions, and context values all differ between the two.
# 1. validate the file before pushing
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# 2. trigger the real event, not workflow_dispatch
git commit --allow-empty -m "ci: verify trigger" && git push
# 3. watch it and read the conclusion, not just the colour
gh run watch
gh run view --log-failedWhat usually goes wrong first
- The workflow file must exist on the default branch before scheduled or dispatch triggers appear at all.
GITHUB_TOKENpermissions default to read-only in many organisations. Declare apermissions:block listing every scope the job needs.- Fork pull requests get a read-only token and no access to secrets, regardless of workflow configuration.
actions/checkoutgives you depth 1 on a detached HEAD, so anything needing history or a branch name needsfetch-depth: 0.