Skip to content
Latchkey

How to Implement /hold and /unhold Merge Gating in GitHub Actions

/hold sets a blocking status so a PR cannot merge, and /unhold clears it, giving reviewers a chat-driven merge brake.

On /hold, add a do-not-merge/hold label and set a failing commit status that is a required check. On /unhold, remove the label and set the status to success. Gate both on an authorized commenter.

Steps

  • Match /hold or /unhold and verify permission.
  • Set a required commit status: failure for hold, success for unhold.
  • Add or remove the do-not-merge/hold label to mirror state.

Hold step

.github/workflows/ci.yml
- uses: actions/github-script@v7
  with:
    script: |
      const held = context.payload.comment.body.trim() === '/hold'
      const pr = await github.rest.pulls.get({
        owner: context.repo.owner, repo: context.repo.repo,
        pull_number: context.issue.number
      })
      await github.rest.repos.createCommitStatus({
        owner: context.repo.owner, repo: context.repo.repo,
        sha: pr.data.head.sha,
        context: 'hold',
        state: held ? 'failure' : 'success',
        description: held ? 'PR is held' : 'Hold cleared'
      })

Gotchas

  • Make the hold status a required check in branch protection or it will not block merges.
  • Only authorized reviewers should be able to hold or release a PR.
  • The status is keyed to the head SHA; a new push resets it, so re-apply if needed.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →