Skip to content

sequelize/draft-unfinished-prs

Repository files navigation

draft-unfinished-prs

A GitHub Action that automatically converts pull requests to draft when they are not ready to be merged, and posts a comment explaining why.

What counts as "unfinished"?

Each criterion is independently configurable:

Criterion Input Default
PR has merge conflicts draft-on-merge-conflict true
PR has any of the specified labels (e.g. wip) draft-on-any-labels (disabled)
PR has an unaddressed change-request review draft-on-changes-requested true
PR has failing required CI checks draft-on-failing-required-checks true

A change-request review is considered unaddressed when a reviewer's latest review state is CHANGES_REQUESTED and the author has not yet re-requested a review from that reviewer (re-requesting adds them back to the Requested reviewers list).

For CI checks the action first tries to read the required status checks from the branch protection rules. If it lacks permission to do so it falls back to flagging any completed check run that has a failing conclusion (failure, timed_out, cancelled, action_required).

Usage

Create a workflow file in your repository, e.g. .github/workflows/draft-unfinished-prs.yml:

name: Draft unfinished PRs

on:
  schedule:
    - cron: '0 5 * * *' # Every day at 05:00 UTC
  workflow_dispatch:    # Allow manual runs from the Actions tab

permissions:
  pull-requests: write  # Convert to draft + post comments
  checks: read          # Read check run results
  contents: read        # Read branch protection rules

jobs:
  draft-unfinished-prs:
    name: Draft unfinished PRs
    runs-on: ubuntu-latest
    steps:
      - uses: sequelize/draft-unfinished-prs@v1
        with:
          # All inputs below are optional — these are the defaults:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          draft-on-merge-conflict: true
          draft-on-any-labels: |    # newline-separated list of labels:
            wip
            do not merge
          draft-on-changes-requested: true
          draft-on-failing-required-checks: true
          post-comment: true
          comment-template: ''      # see "Custom comments" below

On each run the action scans all open, non-draft pull requests and converts any that meet one or more of the unfinished criteria to draft.

Inputs

Input Type Default Description
github-token string ${{ github.token }} GitHub token. Requires pull-requests: write, checks: read, contents: read.
draft-on-merge-conflict boolean true Convert to draft when the PR has merge conflicts.
draft-on-any-labels string '' Convert to draft when the PR has any of these labels. Newline- or comma-separated list. Empty string disables the check.
draft-on-changes-requested boolean true Convert to draft when a reviewer has unaddressed change requests.
draft-on-failing-required-checks boolean true Convert to draft when required CI checks are failing.
dry-run boolean false Run all checks but skip converting to draft and posting comments. What would have happened is printed to the Actions log.
post-comment boolean true Post a comment explaining why the PR was converted to draft.
comment-template string '' Custom comment body. Use {reasons} as a placeholder for the reason list.

Outputs

Output Type Description
drafted JSON string A JSON array of PR numbers that were converted to draft in this run, e.g. [42].

Custom comments

Set comment-template to override the default comment. Use {reasons} to embed the list of reasons:

comment-template: |
  Hey! This PR has been put back into draft:

  {reasons}

  Please fix the above and re-request review when ready. 🙏

Required permissions

The action uses the GITHUB_TOKEN by default. Grant it:

permissions:
  pull-requests: write   # convert to draft + post comments
  checks: read           # read check run results
  contents: read         # read branch protection rules

Note

Draft pull requests are available for public repositories on all plans and for private repositories on GitHub Pro, Team, and Enterprise Cloud.

How it works

scheduled trigger (or workflow_dispatch)
     │
     ▼
list all open, non-draft PRs in the repository
     │
     ▼
for each PR:
  ├─ [merge conflict]       → pr.mergeable === false  (retries up to 3× while null)
  ├─ [label]                → pr.labels intersects draft-on-any-labels list
  ├─ [changes requested]    → latest review state per reviewer is CHANGES_REQUESTED
  │                            AND reviewer is not in requested_reviewers
  └─ [failing CI]           → branch protection → required check names
                               checks API + statuses API: any required check failed
     │
     ▼
  if any reasons found:
    convert to draft + post comment explaining reasons

Development

# Install dependencies
npm ci

# Type-check
npm run lint

# Build dist/index.js
npm run build

The dist/ folder must be committed to the repository. The build.yml workflow rebuilds it automatically on every push to main that touches source files.

About

A github action that automatically drafts PRs that are not finished

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors