{"id":50463914,"url":"https://github.com/pkgdeps/automerge-gate","last_synced_at":"2026-06-01T06:03:57.067Z","repository":{"id":355848363,"uuid":"1229895260","full_name":"pkgdeps/automerge-gate","owner":"pkgdeps","description":"A single required status check that gates Enable Auto Merge on every CI run that lands on a PR. Merge GateKeeper.","archived":false,"fork":false,"pushed_at":"2026-05-26T05:40:06.000Z","size":319,"stargazers_count":30,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-26T07:25:40.112Z","etag":null,"topics":["github-actions"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pkgdeps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-05T13:37:29.000Z","updated_at":"2026-05-26T05:40:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pkgdeps/automerge-gate","commit_stats":null,"previous_names":["pkgdeps/check-suite-gate","pkgdeps/automerge-gate"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/pkgdeps/automerge-gate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkgdeps%2Fautomerge-gate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkgdeps%2Fautomerge-gate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkgdeps%2Fautomerge-gate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkgdeps%2Fautomerge-gate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pkgdeps","download_url":"https://codeload.github.com/pkgdeps/automerge-gate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkgdeps%2Fautomerge-gate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33762215,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github-actions"],"created_at":"2026-06-01T06:03:55.311Z","updated_at":"2026-06-01T06:03:57.055Z","avatar_url":"https://github.com/pkgdeps.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# automerge-gate\n\nA single required check that gates **Enable Auto Merge** on every CI run that lands on a PR.\n\n## Why\n\nGitHub's branch protection / rulesets ask you to list each required status check by name. That list is fragile:\n\n- Renovate / Dependabot bring in checks from external GitHub Apps that come and go.\n- Monorepos use path filters, so a workflow may be skipped on some PRs and present on others.\n- Adding a new workflow file means rewriting the ruleset.\n\nautomerge-gate replaces that list with **one aggregated check**. You register only that single check (`automerge-gate/all-passed`) as the required check in your ruleset. When a maintainer clicks Enable Auto Merge, the action waits for every check on the PR — across workflow files, across GitHub Apps — then reports the verdict (in private mode, as a commit status on the head SHA; in public mode, via the gate job's exit code). GitHub's native auto-merge takes the PR from there.\n\n- Related: [Is it possible to require all GitHub Actions tasks to pass without enumerating them? · community · Discussion #26733](https://github.com/orgs/community/discussions/26733)\n- Migrating from merge-gatekeeper? See the [migration guide](docs/migration-from-merge-gatekeeper.md).\n\n## How it works\n\nautomerge-gate ships in two modes, with different gate-signal mechanics. Pick the one that matches your repository's fork-PR posture (see [Usage](#usage) for the trade-offs).\n\n### Private mode\n\nCost-optimized: PRs without merge intent skip polling so runner minutes are saved. The action writes the aggregated verdict as a commit status via the legacy Commit Status API.\n\n```mermaid\nsequenceDiagram\n    participant U as Maintainer\n    participant A as automerge-gate (action)\n    participant PR as Pull Request\n\n    U-\u003e\u003ePR: open / push\n    Note over A: action skips (no merge intent)\n    Note over PR: required check at \"Expected — Waiting for status to be reported\" → merge blocked\n\n    U-\u003e\u003ePR: Enable Auto Merge or Approve\n    A-\u003e\u003eA: poll every other check on the PR\n\n    alt all checks pass\n        A-\u003e\u003ePR: POST commit status → success\n        PR-\u003e\u003ePR: GitHub auto-merge → merged\n    else any check fails\n        A-\u003e\u003ePR: POST commit status → failure\n        Note over PR: merge blocked\n    end\n```\n\n1. A PR is opened. There's no merge intent yet, so the action skips without writing a status. The required check stays at GitHub's default `Expected — Waiting for status to be reported`, which keeps the PR blocked.\n2. The maintainer clicks **Enable Auto Merge**, _or_ a reviewer with write access submits an **Approve** review. The action enters polling mode and watches every other check on the PR.\n3. The action polls every other check on the PR, applying any `ignore-checks` filters.\n4. After polling, the action writes the aggregated verdict (`state: success` or `failure`) as a commit status on the head SHA, keyed by the configured `context`. GitHub's required-check evaluation looks up the same `(SHA, context)` pair, so the verdict turns the required check green or red immediately.\n5. GitHub's native auto-merge fires when the required check turns green.\n\nIf Auto Merge is already enabled when you push a new commit, the gate re-evaluates the new SHA automatically — no need to disable→enable. Commit status is keyed by `(SHA, context)`, so there's no per-SHA cleanup: each push targets a fresh SHA whose status starts blank until the gate posts a verdict.\n\nIf your team uses an Approve review to mean \"looks good\" rather than \"ready to merge\", remove `pull_request_review` from the workflow's `on:` triggers. The gate then only enters polling mode when Auto Merge is explicitly enabled.\n\n### Public mode\n\nFork-aware: `GITHUB_TOKEN` is read-only on fork PRs, so the gate signal is the gate **job's** own auto-created check_run conclusion. The job runs on every triggering event and always polls; there is no skip path.\n\n```mermaid\nsequenceDiagram\n    participant PR as Pull Request\n    participant J as gate job\n    participant A as automerge-gate (action)\n\n    PR-\u003e\u003eJ: workflow triggered (always)\n    Note over J: job's check_run = required-check context (job name matches)\n    J-\u003e\u003eA: action starts → polls every other check on the PR\n\n    alt all checks pass\n        A-\u003e\u003eJ: exit 0\n        J-\u003e\u003ePR: job's check_run → success\n        PR-\u003e\u003ePR: GitHub auto-merge → merged\n    else any check fails\n        A-\u003e\u003eJ: exit non-zero\n        J-\u003e\u003ePR: job's check_run → failure\n        Note over PR: merge blocked\n    end\n```\n\n1. The PR is opened (or pushed to). The workflow always triggers. GitHub Actions auto-creates a check_run named after the gate job (e.g. `automerge-gate/all-passed`) — that check_run is the required-check signal.\n2. The action polls every other check on the PR, applying any `ignore-checks` filters.\n3. After polling, the action exits 0 (success) or non-zero (failure). The job's check_run conclusion follows the exit code, and GitHub treats it as the required check's verdict.\n4. GitHub's native auto-merge fires when the required check turns green.\n\nThe action does not write its own check_run in this mode (the JOB's auto-created one is the gate). Read-only `checks: read` permission is sufficient; add `actions: read` if `ignore-checks` uses a `workflow` rule (the action resolves run-to-workflow paths via the Actions API).\n\nNote: GitHub rulesets only support AND across required checks (no OR / conditional logic), so this action is the place where \"all of these checks across workflows must pass\" is expressed as a single check.\n\n## Usage\n\nSetup is five steps: pick a mode, add the workflow file, register the required check in the ruleset, allow auto-merge in repository settings, then click Enable Auto Merge on a PR. Run them in order — Step 5 won't show the button until Step 4 is done, and the required check from Step 3 only gates merges once a workflow run reports it.\n\n### Step 1: Pick a mode\n\nChoose based on whether your repository accepts external fork PRs.\n\n- Private mode (cost-optimized) — internal-only repos that do not receive external fork PRs. The action writes the aggregated verdict as a commit status via the legacy Commit Status API. PRs without merge intent skip polling entirely so runner minutes are saved.\n- Public mode (fork-aware) — repos that accept fork PRs. `GITHUB_TOKEN` is read-only on fork PRs, so the gate signal is the gate job's own check_run conclusion. The job runs on every triggering event and always polls.\n\n|                               | private                            | public                 |\n| ----------------------------- | ---------------------------------- | ---------------------- |\n| `pull_request_review` trigger | yes                                | no                     |\n| Job `name:`                   | (default)                          | matches required check |\n| Permissions                   | `statuses: write` + `checks: read` (+ `actions: read` for `workflow` rules) | `checks: read` (+ `actions: read` for `workflow` rules) |\n| API write of aggregate        | yes (commit status)                | no (job exit code)     |\n| Skip on no merge intent       | yes (saves runner minutes)         | no (always polls)      |\n\n### Step 2: Add the workflow file\n\nCreate `.github/workflows/automerge-gate.yaml` using the YAML for the mode picked in Step 1.\n\n#### Private mode\n\n```yaml\nname: automerge-gate\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened, auto_merge_enabled]\n  pull_request_review:\n    types: [submitted]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  gate:\n    if: \u003e-\n      github.event_name != 'pull_request_review' ||\n      github.event.review.state == 'approved'\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    permissions:\n      statuses: write\n      checks: read\n      pull-requests: read\n      actions: read\n    steps:\n      - uses: pkgdeps/automerge-gate@v4.1.0\n        with:\n          gate-mode: 'private'\n          context: 'automerge-gate/all-passed'\n```\n\nThe `pull_request_review.state == 'approved'` clause filters out non-Approve review submissions (`commented`, `changes_requested`) at the job level, so the runner doesn't even spin up for those. GitHub's `on:` block can filter activity types but not review state, so the filter has to live in the job's `if:`.\n\nWhen a `synchronize`, `opened`, or `reopened` event fires without an active merge intent (no Auto Merge enabled, no sticky write-permission Approve), the action exits cleanly without writing a status. The required check stays at GitHub's `Expected` state and merge stays blocked, but no polling burns runner minutes.\n\n#### Public mode\n\n```yaml\nname: automerge-gate\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened, auto_merge_enabled]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  gate:\n    name: automerge-gate/all-passed # must match the required check in your ruleset\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    permissions:\n      checks: read\n      pull-requests: read\n      actions: read\n    steps:\n      - uses: pkgdeps/automerge-gate@v4.1.0\n        with:\n          gate-mode: 'public'\n```\n\nIn public mode, the job's `name:` _is_ the required-check context — GitHub Actions creates a check_run with that name when the job starts, and its conclusion is what the required check evaluates. The action polls every triggering event (no skip path) because the read-only token can't write a \"waiting\" signal anyway, and a skipped job would let an unfinished PR slip through merge.\n\n### Step 3: Register the required check\n\nOpen Settings → Rules → Rulesets (or Branches → Branch protection) and add a rule that requires the check `automerge-gate/all-passed`.\n\n\u003e [!WARNING]\n\u003e The autocomplete only lists checks that have already run on this repo, so the dropdown is empty on first setup. Type `automerge-gate/all-passed` by hand — both rulesets and branch protection accept any name. Once the gate runs on a PR, it shows up in the dropdown.\n\nThis single required check is now the only thing standing between a PR and merge. Any check that lands on the PR — Renovate, Codecov, your own workflows — gets aggregated into it.\n\n### Step 4: Allow auto-merge\n\nOpen Settings → General → Pull Requests and tick **Allow auto-merge**. Without this the _Enable Auto Merge_ button doesn't show up on PRs, so Step 5 has nothing to click.\n\n### Step 5: Enable Auto Merge on a PR\n\n1. Get the PR ready (review, fix, etc.).\n2. Click **Enable Auto Merge**.\n3. The gate job runs, polls every check on the PR, then exits with `success` (or fails the job on aggregated failure).\n4. On success, GitHub's native auto-merge fires immediately and merges the PR. On failure, auto-merge is blocked; fix and push again — as long as Auto Merge stays enabled, the gate re-evaluates the new SHA on every push.\n\n\u003e [!IMPORTANT]\n\u003e The action does **not** expose a timeout input. The job-level `timeout-minutes` is the only bound on how long the polling loop runs, and you should treat it as part of the action's configuration. There are no two timeouts to keep in sync — just one. If your CI runs longer than 10 minutes, raise `timeout-minutes` accordingly.\n\n## Inputs\n\n| name                    | required | default                     | description                                                                                                                                                                                                                                                                                                          |\n| ----------------------- | -------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `gate-mode`             | **yes**  | (none)                      | `private` / `public`. `private` = action writes the aggregated commit status via the legacy Commit Status API (token needs `statuses: write` + `checks: read`). `public` = gate signal is the JOB's own check_run conclusion; the job's `name:` must match the required-check context (token can be `checks: read`). Either mode additionally needs `actions: read` when `ignore-checks` contains a `workflow` rule. |\n| `context`               | no       | `automerge-gate/all-passed` | Aggregated commit status context. **`gate-mode: private` only** — must match the required check in your ruleset. Ignored when `gate-mode: public` (the job name is the signal).                                                                                                                                      |\n| `poll-interval-seconds` | no       | `30`                        | How often to re-fetch check status                                                                                                                                                                                                                                                                                   |\n| `ignore-checks`         | no       | `[]`                        | JSONC array of rules to exclude check_runs from aggregation. Each rule is `{ app?, workflow?, name? }`; fields are AND-evaluated and every field is a glob (`*` / `?`). See [Examples](#examples).                                                                                                                   |\n| `token`                 | no       | `${{ github.token }}`       | GitHub token used to read checks and (when permitted) write the aggregated commit status                                                                                                                                                                                                                             |\n\nThere is **no `timeout-seconds` input on purpose** — timeout is delegated entirely to the job's `timeout-minutes` so there's a single source of truth. See the IMPORTANT note in the Usage section above.\n\n### Examples\n\n#### `ignore-checks`\n\n`ignore-checks` is a JSONC array of rules. Each rule is an object with optional fields:\n\n| field      | matches against                             | notes                                                                                                                                                                                       |\n| ---------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `app`      | The originating GitHub App's slug           | Internally the action reads the slug from the check_run's parent check_suite. See [Discovering what to ignore](#discovering-what-to-ignore) below for the inspection command.               |\n| `workflow` | Basename of the workflow file               | GitHub Actions only; third-party Checks (no workflow file) never match a rule with `workflow` set. **Requires the workflow's token to have `actions: read` permission** — the action resolves each run's workflow path via the Actions API, and without that scope the lookup returns `null` so the rule never matches. |\n| `name`     | `check_run.name`                            | This is `jobs.\u003ckey\u003e.name` (or `jobs.\u003ckey\u003e` if `name:` is omitted), **not** the `\u003cworkflow\u003e / \u003cjob\u003e` string the UI shows.                                                                    |\n\nWithin a single rule, all present fields must match (AND); absent fields are wildcards. Every field is a glob — `*` matches any run of characters, `?` matches one character. Across rules, the action excludes a check_run if **any** rule matches.\n\nJSONC means standard JSON plus `//` / `/* */` comments and trailing commas — so this is legal:\n\n```yaml\nwith:\n  ignore-checks: |\n    [\n      { \"app\": \"dependabot\" },                                  // ignore everything from dependabot\n      { \"app\": \"renovate\" },\n      { \"name\": \"optional-*\" },                                 // glob across all workflows / apps\n      { \"app\": \"xcode-cloud\", \"name\": \"Build *\" },              // only xcode-cloud's build jobs\n      { \"workflow\": \"ci-go.yaml\", \"name\": \"lint\" },             // only ci-go.yaml's lint job (GitHub Actions)\n    ]\n```\n\n#### Discovering what to ignore\n\nThe schema is shaped to mirror `gh api ... | jq` output, so you can inspect a PR's checks and paste the rows you want to ignore directly into `ignore-checks`.\n\n```bash\ngh api --paginate --slurp \"repos/{owner}/{repo}/commits/{sha}/check-runs\" \\\n  | jq '[.[].check_runs[] | { app: .app.slug, name } | with_entries(select(.value != null))]'\n```\n\n`--slurp` cannot be combined with `gh`'s built-in `--jq`, so the pipe to an external `jq` is intentional. `--slurp` collects all paginated pages into one array; the `jq` expression then flattens `.check_runs[]` across pages so the result is a single rule list rather than one array per page.\n\nThis emits one `{ app, name }` row per check_run. Pick the rows you want to silence and paste them into `ignore-checks` as-is — each row is already a valid rule:\n\n```yaml\nwith:\n  ignore-checks: |\n    [\n      { \"app\": \"github-actions\", \"name\": \"optional-flaky\" },\n      { \"app\": \"xcode-cloud\", \"name\": \"Build (release)\" }\n    ]\n```\n\nWhen the same `name` repeats across rows, those are separate check_runs from different workflows whose job names happen to collide — a common monorepo pattern:\n\n```json\n[\n  { \"app\": \"github-actions\", \"name\": \"check\" },\n  { \"app\": \"github-actions\", \"name\": \"check\" },\n  { \"app\": \"github-actions\", \"name\": \"gate\" },\n  { \"app\": \"github-actions\", \"name\": \"check\" }\n]\n```\n\nTo ignore one of them but not the others, add a `workflow` field to disambiguate. The `workflow` field is not in the inspection command above, but you can join in the workflow path via `check_suite_id` with a second `gh api` call against `/actions/runs?head_sha={sha}`:\n\n```bash\nSHA=...\nruns=$(gh api --paginate --slurp \"repos/{owner}/{repo}/actions/runs?head_sha=$SHA\" \\\n  | jq 'map(.workflow_runs[]) | map({ (.check_suite_id|tostring): (.path | split(\"/\") | last) }) | add')\n\ngh api --paginate --slurp \"repos/{owner}/{repo}/commits/$SHA/check-runs\" \\\n  | jq --argjson runs \"$runs\" '[\n      .[].check_runs[]\n      | { app: .app.slug, workflow: $runs[.check_suite.id|tostring], name }\n      | with_entries(select(.value != null))\n    ]'\n```\n\n`workflow` is `null` (and therefore dropped from the row) for third-party Checks that don't originate from a GitHub Actions workflow. The second call requires the same `actions: read` token scope that the action itself uses for `workflow` rules. Paste the disambiguating row into `ignore-checks`:\n\n```yaml\nwith:\n  ignore-checks: |\n    [\n      { \"app\": \"github-actions\", \"workflow\": \"ci-go.yaml\", \"name\": \"check\" }\n    ]\n```\n\nThe command covers **check_runs only** — the data source `ignore-checks` filters against. It does not include legacy commit statuses (`/commits/{sha}/status`) or PR reviews (`/pulls/{n}/reviews`). automerge-gate likewise reads only check_runs (plus, in `gate-mode: private`, PR reviews for the approval signal); legacy commit statuses are not evaluated. Signals such as Copilot Code Review surface as PR reviews and never appear in `/check-runs`.\n\n#### Tune polling interval for fast CI\n\n```yaml\n- uses: pkgdeps/automerge-gate@v4.1.0\n  with:\n    gate-mode: 'private'\n    poll-interval-seconds: '10'\n```\n\n## Outputs\n\n| name                | description                                    |\n| ------------------- | ---------------------------------------------- |\n| `state`             | `success` / `failure` / `skipped`              |\n| `total-checks`      | Number of check_runs observed before filtering |\n| `evaluated-checks`  | Number of check_runs after filters             |\n| `completed-checks`  | Number of completed check_runs after filters   |\n| `polled-iterations` | Number of polling iterations performed         |\n\n## Limitations\n\n- **Merge queue (`merge_group`)** is not supported.\n- **Dead runner / job timeout**: the polling job can be killed before it finishes (its `timeout-minutes` fires, the runner dies, and so on). The gate job's check_run then ends as `failure` or `cancelled`. The required check stays red and merge stays blocked. To retry, disable Auto Merge and enable it again.\n- **CIs that only write legacy commit statuses**: GitHub has two CI reporting APIs. Modern CIs (GitHub Actions, Cloudflare Pages, Codecov) use the check_run / check_suite API. Some older or self-hosted CIs (Atlantis, some Jenkins setups) only use the legacy commit-status API. This action only reads the check_run / check_suite side, so a CI that only writes legacy commit statuses is not aggregated. If you depend on such a CI, add it as a separate required check in your ruleset alongside `automerge-gate/all-passed`.\n\n## Versioning\n\nReleases are published as **immutable semver tags** (`v1.0.0`, `v1.1.0`, ...). There is intentionally no moving major tag (`v4`) — pin a fixed version in your workflow and let Renovate / Dependabot open PRs when a new version ships. This eliminates the supply-chain risk of a moving tag being silently rewritten.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkgdeps%2Fautomerge-gate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpkgdeps%2Fautomerge-gate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkgdeps%2Fautomerge-gate/lists"}