← Back to Blog

How Do I Check If an OSS Repo Auto-Closes External Pull Requests?

By Nark Team

Before preparing a pull request to an open-source repository, grep its .github/workflows/ directory for actions-cool/verify-files-modify, forbid-paths, and pull_request_target. Repos that protect maintainer-only paths (CI configs, release scripts, CHANGELOGs, LICENSE) wire those workflows to auto-close any external PR that touches the forbidden paths within seconds of opening — before any human reviewer sees the submission. The bypass mechanisms (skip-contribution-count, skip-verify-authority, skip-label) require either a track record in the repo or maintainer pre-approval. A 30-second audit prevents preparing a PR that has zero chance of merging.

Quick Answer: Run grep -rln "verify-files-modify\|forbid-paths" .github/workflows/ in the repo you want to contribute to. If any workflow matches, read it and confirm your target file is not under a forbid-paths entry. If it is, file an issue with a patch instead of opening a PR — the auto-close runs on pull_request_target: opened and overwrites your PR description with a templated comment before maintainers respond.


What Is verify-files-modify and Why Do Repos Use It?

actions-cool/verify-files-modify is a GitHub Action that auto-closes pull requests touching protected paths or files. Maintainers wire it into their CI to enforce "maintainer-only" policies on:

  • .github/workflows/ — to prevent supply-chain attacks via malicious CI changes
  • scripts/ — to keep release scripts and build tooling under maintainer control
  • CHANGELOG.md / CHANGELOG.*.md — to prevent drive-by edits to historical release notes
  • LICENSE — to prevent license tampering
  • README.md — to keep the project's public-facing copy authoritative

The action runs on pull_request_target (the elevated-permission variant of pull_request), so it can post comments and close PRs even when triggered by external contributors. The configuration is declarative — a single with: block lists forbidden paths, a comment template, and bypass conditions.

# .github/workflows/verify-files-modify.yml (typical config)
name: Verify Files modify
on:
  pull_request_target:
    types: [opened, synchronize]
permissions:
  contents: read
jobs:
  verify:
    if: github.event.pull_request.user.login != 'renovate[bot]'
    permissions:
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions-cool/verify-files-modify@a54698778b0a8570ffc245c2a03ac80be849c9fb
        with:
          forbid-paths: '.github/, scripts/'
          forbid-files: 'CHANGELOG.zh-CN.md, CHANGELOG.en-US.md, LICENSE'
          skip-contribution-count: 10
          skip-verify-authority: write
          skip-label: skip-verify-files
          assignees: 'maintainer1, maintainer2'
          comment: |
            Hi @${{ github.event.pull_request.user.login }}. The path ... is only maintained by team members. This current PR will be closed and team members will help on this.
          close: true
          set-failed: false

The policy is reasonable. External PRs touching CI workflows are a common attack vector (pwn requests), and CHANGELOGs are usually generated automatically. The cost is real for legitimate external contributors: the bot drives the PR into the closed state before any human reviewer reads the description.


How Does GitHub Auto-Close a Pull Request Mechanically?

GitHub Actions workflows declared with on: pull_request_target execute against the target branch's workflow file, not the PR's own copy. This is what makes them safe to give elevated permissions — external contributors can't modify the workflow itself to escalate.

When the PR is opened:

  1. GitHub fires the pull_request_target event with action: opened.
  2. The workflow's if: condition runs. Most workflows exclude known bot accounts (renovate, dependabot, Copilot) here.
  3. The action reads the PR's changed-file list via the GitHub API.
  4. It cross-references the list against forbid-paths (directory prefixes) and forbid-files (literal filenames).
  5. If any match, it checks the bypass conditions in order:
    • skip-contribution-count — does the PR author have at least N merged PRs to this repo?
    • skip-verify-authority — does the author have at least write (or admin) on this repo?
    • skip-label — is the specified label present on the PR? (Maintainers can apply this label to grant a one-off exemption.)
  6. If no bypass matches, the action posts the templated comment on the PR thread and calls PATCH /repos/{owner}/{repo}/pulls/{number} with state: closed.

Total elapsed time: typically 10-30 seconds from PR open. The author's PR description, no matter how detailed, lives below the bot's comment on the thread — and the PR is closed, signaling to other reviewers that no action is needed.


What Does the ant-design Auto-Close Look Like in Practice?

ant-design (91k+ stars) protects .github/, scripts/, and both CHANGELOG files with this action. The auto-close is reproducible across many recent external PRs. PR #57459 by dagecko is a representative example:

  • PR author: dagecko (0 prior merged PRs to ant-design)
  • PR scope: security hardening — pinning 57 unpinned GitHub Actions and extracting 3 unsafe expressions to env vars across .github/workflows/*.yml
  • PR description: a thoughtful security audit referencing CodeQL/Semgrep findings
  • Outcome: Auto-closed on 2026-03-26 with the templated bot comment:

Hi @dagecko. Thanks for your contribution. The path .github/ or scripts/ and CHANGELOG is only maintained by team members. This current PR will be closed and team members will help on this.

A maintainer later applied the skip-verify-files label, but the PR stayed in the closed state. Maintainers eventually applied parts of the security hardening themselves in a separate commit. The patch helped the project; dagecko was not credited in the resulting merge.

This is the canonical pattern: the contribution surfaces a real issue, the maintainer team handles it internally, and the external contributor's PR history shows one closed pull request with no merge attribution. The policy is working as intended from the maintainer's perspective; the contributor's incentive to file the patch in the first place is reduced.


How Do I Audit a Repo's Workflow Gates in 30 Seconds?

Run this in the cloned repo before branching:

# 1. Look for known auto-close actions
grep -rln "actions-cool/verify-files-modify\|verify-files-modify@" .github/workflows/

# 2. Look for forbid-paths / forbid-files declarations directly
grep -rn "forbid-paths\|forbid-files\|forbid-branch" .github/workflows/

# 3. Look for "team members only" phrasing in workflow comments
grep -rn "only maintained by\|team members\|maintainer-only" .github/workflows/

# 4. Look for issue-first policies
grep -rin "open an issue first\|issue before\|require an issue" CONTRIBUTING.md .github/CONTRIBUTING.md

If any of step 1-3 match, open the workflow file and read the forbid-paths / forbid-files lines. If your target file matches any of them, do not open a PR — file an issue with a patch instead (see below).

You can also check the GitHub API for recent auto-closed PRs to confirm the policy is active:

gh search prs --repo <org>/<repo> "is only maintained by team members" --state closed --limit 5

A list of recent matches means the action is firing and closing external PRs in production. An empty list means either the action isn't deployed yet or the comment template differs.


What Workflow Patterns Block External Contributors?

The verify-files-modify pattern is the most common, but there are several related gates:

PatternWhat It DoesTypical Bypass
actions-cool/verify-files-modifyAuto-close PRs touching forbid-paths or forbid-filesMerged-PR count, write access, manual label
peter-evans/close-issue / close-prGeneric auto-close on conditionsRepo-specific config
Branch protection: Require linear historyReject merge commitsRebase locally
Branch protection: Required signed commitsReject unsigned commitsgit config commit.gpgSign true
dco-botRequire Signed-off-by: trailer on every commitgit commit --signoff or rebase with --signoff
cla-assistant / EasyCLABlock merge until CLA signedOne-time signature via bot link
Issue-first policy (in CONTRIBUTING.md)Maintainers close drive-by PRs without issueFile issue first, link in PR
Stale-PR auto-close (after N days no response)Close inactive PRsComment to keep alive

Of these, verify-files-modify is the only one that runs before human review and is not recoverable without maintainer intervention. CLA and DCO are gates on merge, not on opening; signed-commits and linear-history are recoverable by rebasing. verify-files-modify is unique in that the PR is dead-on-arrival.


What Should I Do If a Repo Gates the File I Want to Change?

Three pragmatic paths, in order of friction:

1. File an issue with the patch inline

# Diagnostic improvement for scripts/check-version-md.ts

I noticed `scripts/check-version-md.ts:52` emits the same "wrongly written"
message for three distinct failure modes (malformed date, out-of-range,
semantic typo). Here's the diff that disambiguates the malformed case:

​```diff
-    const date = dayjs(text.trim().replace('`', '').replace('`', ''));
+    const rawDate = text.trim().replace('`', '').replace('`', '');
+    const date = dayjs(rawDate);
+    if (!date.isValid()) {
+      console.log(`Changelog date "${rawDate}" could not be parsed`);
+      process.exit(1);
+    }
​```

`verify-files-modify` will auto-close any PR I open against `scripts/`,
so filing as an issue. Happy to open a PR if you'd like to pre-label it
with `skip-verify-files`.

Issues are not gated by verify-files-modify (the workflow only triggers on pull_request_target). The maintainer sees the patch in their issue tracker. They can either apply it themselves (most likely) or grant the label exemption and ask you to open a PR.

2. Build a contribution track record on non-gated paths first

If the repo allows external PRs to src/, components/, or docs/, ship 10 small merge-friendly PRs there. After 10 merges, your account hits the skip-contribution-count threshold and future PRs touching protected paths bypass the gate automatically. This is the path the gate's design implies — long-term contributors earn protected-path access.

3. Skip the repo

Not every gated repo is worth the effort. A 91k-star project with active maintainers will likely apply your issue's patch on their own schedule. Your time may be better spent on a repo where the contribution will land with author credit.


Why Doesn't This Show Up in CONTRIBUTING.md?

Most CONTRIBUTING.md files predate the auto-close workflow. The repo's contribution guide reads like "we welcome external contributions" but the CI behavior on protected paths is "we don't, for these specific paths." This mismatch is common: the docs reflect the project's culture, while the workflow reflects the security posture.

You can't fix this by reading docs alone. The workflow file is the source of truth. The audit step above is the only reliable way to know what will actually happen when you open the PR.


Frequently Asked Questions

Does the auto-close apply to bot-authored PRs (dependabot, renovate)?

Usually no. The standard verify-files-modify configuration includes if: github.event.pull_request.user.login != 'renovate[bot]' and similar excludes for known bots. Maintainers add specific bot accounts to the allow-list because dependency-update bots legitimately need to modify CI files. If your account isn't on the allow-list, you're treated as external.

Can I avoid the auto-close by force-pushing after opening?

No. The workflow re-runs on synchronize (every push to the PR branch). Force-pushing triggers another auto-close evaluation. The bypass conditions don't change.

Can a maintainer reopen the PR after the auto-close fires?

Yes — maintainers can reopen any PR. In practice, this rarely happens. The bot's comment signals to the rest of the team that the issue is in the queue, and maintainers apply the patch in their own commit rather than re-opening someone else's PR.

Are there other actions like verify-files-modify?

Yes. dorny/paths-filter can be wired to label-and-close. Some repos use custom JavaScript actions for the same purpose. peter-evans/close-pull is sometimes scripted with paths-filter. The grep recipe above catches the common patterns; for custom actions, search for close: true or gh pr close invocations across the workflows directory.

What about pull_request vs pull_request_target?

pull_request runs in a sandboxed environment without access to repo secrets and cannot write to PRs (post comments, close, label). pull_request_target runs with the target branch's permissions and CAN write to PRs. The auto-close pattern requires pull_request_target. If you see on: pull_request in a workflow, it cannot close the PR — only fail status checks.


How Nark Helps Here

Nark is a static analyzer that scans TypeScript projects for missing error handling, unsafe parsing, and unguarded API calls — defined via open-source Nark Profiles. The most common flow for Nark users is "find a bug in a popular repo, prepare a fix, open a PR."

For repos with protected paths, that flow breaks at PR-open time. The fix is real, the diff is clean, the tests pass — but the bot closes the PR before maintainers see it. Adding the workflow audit above to your pre-flight checklist saves the wasted prep time.

The companion check: confirm that your target file lives outside forbid-paths / forbid-files. If Nark flags a violation under scripts/ in a protected repo, your options are issue-with-patch, long-term contribution track record, or different repo.

# Pre-flight before preparing an OSS PR
git clone <repo>
cd <repo>
grep -rln "verify-files-modify\|forbid-paths" .github/workflows/
# If matched, read the workflow and check your target against forbid-paths/forbid-files

The check costs 30 seconds and reliably catches the auto-close trap before you invest in a fix.


Try Nark Now

npx nark --tsconfig ./tsconfig.json

Nark scans your TypeScript codebase against 165+ Nark Profiles covering axios, prisma, stripe, dayjs, zod, redis, and more. Pair it with the workflow audit above to ship fixes only to repos that will actually accept them.