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 aforbid-pathsentry. If it is, file an issue with a patch instead of opening a PR — the auto-close runs onpull_request_target: openedand 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 changesscripts/— to keep release scripts and build tooling under maintainer controlCHANGELOG.md/CHANGELOG.*.md— to prevent drive-by edits to historical release notesLICENSE— to prevent license tamperingREADME.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:
- GitHub fires the
pull_request_targetevent withaction: opened. - The workflow's
if:condition runs. Most workflows exclude known bot accounts (renovate, dependabot, Copilot) here. - The action reads the PR's changed-file list via the GitHub API.
- It cross-references the list against
forbid-paths(directory prefixes) andforbid-files(literal filenames). - 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 leastwrite(oradmin) on this repo?skip-label— is the specified label present on the PR? (Maintainers can apply this label to grant a one-off exemption.)
- If no bypass matches, the action posts the templated
commenton the PR thread and callsPATCH /repos/{owner}/{repo}/pulls/{number}withstate: 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/orscripts/andCHANGELOGis 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:
| Pattern | What It Does | Typical Bypass |
|---|---|---|
actions-cool/verify-files-modify | Auto-close PRs touching forbid-paths or forbid-files | Merged-PR count, write access, manual label |
peter-evans/close-issue / close-pr | Generic auto-close on conditions | Repo-specific config |
Branch protection: Require linear history | Reject merge commits | Rebase locally |
Branch protection: Required signed commits | Reject unsigned commits | git config commit.gpgSign true |
dco-bot | Require Signed-off-by: trailer on every commit | git commit --signoff or rebase with --signoff |
cla-assistant / EasyCLA | Block merge until CLA signed | One-time signature via bot link |
| Issue-first policy (in CONTRIBUTING.md) | Maintainers close drive-by PRs without issue | File issue first, link in PR |
| Stale-PR auto-close (after N days no response) | Close inactive PRs | Comment 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.