_____ _ _____ _
/ ____| | / ____| | |
| (___ | | ___ _ __| | __ __ _| |_ ___
\___ \| |/ _ \| '_ \ | |_ |/ _` | __/ _ \
____) | | (_) | |_) | |__| | (_| | || __/
|_____/|_|\___/| .__/ \_____|\__,_|\__\___|
| |
|_|
The AI slop firewall for open source maintainers.
Open source is drowning in low-quality pull requests generated by AI tools and submitted without review. The Godot engine maintainers were overwhelmed by mass AI-generated PRs that wasted hundreds of hours of reviewer time. curl shut down its bug bounty program after a flood of AI-generated reports that were worse than useless. GitHub itself has discussed implementing a kill switch for AI-generated contributions. SlopGate gives you that kill switch today -- a configurable, automated defense against the wave of low-effort AI-generated pull requests flooding open source.
Add this workflow to your repository at .github/workflows/slopgate.yml:
name: SlopGate
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
pull-requests: write
issues: write
jobs:
slopgate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: A386official/slopgate@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}That's it. Every PR will be analyzed and scored automatically.
SlopGate runs 12 independent detectors across three categories, produces a weighted score from 0-100, and responds accordingly:
| Score Range | Verdict | Action |
|---|---|---|
| 0-29 | Pass | Green label added. No comment. |
| 30-59 | Warn | Yellow label + comment with findings. |
| 60-79 | Flag | Red label + changes requested with detailed analysis. |
| 80-100 | Block | Block label + full report. Optionally auto-closes the PR. |
SlopGate analyzes three signal categories:
| Check | What It Detects | Default Weight |
|---|---|---|
| PR Velocity | >3 PRs to the same repo in 24 hours | 80 |
| Abandonment Rate | High percentage of closed-without-merge PRs | 60 |
| Shotgun Pattern | Identical PRs submitted across multiple repositories | 90 |
| New Account | Account <30 days old submitting first PR to the repo | 20 |
| Check | What It Detects | Default Weight |
|---|---|---|
| Placeholder Code | Empty functions, bare pass statements, TODO stubs, placeholder variable names |
70 |
| Hallucinated Imports | Import statements referencing packages not in the project's dependencies | 90 |
| Docstring Inflation | Comment-to-code ratio above 60%, padding thin contributions with verbose docs | 40 |
| Internal Duplication | Large blocks of identical code copy-pasted within the diff | 60 |
| Check | What It Detects | Default Weight |
|---|---|---|
| Generic Description | Vague titles like "fix bug" or "update code" with no meaningful body | 50 |
| Oversized Diff | 500+ line changes with a one-sentence description | 60 |
| Unrelated Changes | Files spanning 5+ unrelated directories in a single PR | 40 |
| Formatting Only | Whitespace-only changes that claim to fix bugs or add features | 30 |
Each check produces a raw score from 0-100. The final score is computed as a weighted average:
finalScore = sum(check.score * check.weight) / sum(check.weight)
Checks with weight 0 are skipped entirely. The verdict is determined by comparing the final score against configurable thresholds.
Create a .slopgate.yml file in your repository root to customize behavior:
# Score thresholds for each response tier
thresholds:
warn: 30 # 30-59: review label + comment
flag: 60 # 60-79: flagged label + request changes
block: 80 # 80+: blocked label + optional auto-close
# Auto-close PRs that score above the block threshold
# WARNING: Use with caution. Defaults to false.
auto_close: false
# Check weights (0-100, set to 0 to disable a check)
weights:
velocity: 80
abandonment: 60
shotgun: 90
new_account: 20
placeholder: 70
hallucinated_import: 90
docstring_inflation: 40
copy_paste: 60
generic_description: 50
oversized_diff: 60
unrelated_changes: 40
formatting_only: 30
# Allowlist -- PRs from these users/bots skip all checks
allowlist:
users: []
bots:
- dependabot[bot]
- renovate[bot]
- github-actions[bot]| Field | Type | Default | Description |
|---|---|---|---|
thresholds.warn |
number | 30 | Minimum score to trigger a warning |
thresholds.flag |
number | 60 | Minimum score to flag and request changes |
thresholds.block |
number | 80 | Minimum score to block (and optionally close) |
auto_close |
boolean | false | Automatically close PRs above block threshold |
weights.<check> |
number | varies | Weight for each check (0 disables it) |
allowlist.users |
string[] | [] | Usernames that bypass all checks |
allowlist.bots |
string[] | [dependabot, renovate, github-actions] | Bot accounts that bypass all checks |
| Input | Required | Default | Description |
|---|---|---|---|
github-token |
Yes | ${{ github.token }} |
GitHub token with pull-requests: write and issues: write permissions |
| Output | Description |
|---|---|
score |
Final SlopGate score (0-100) |
verdict |
Verdict: pass, warn, flag, or block |
allowlisted |
Whether the PR author was allowlisted |
summary |
Human-readable summary of findings |
Use outputs in subsequent workflow steps:
- uses: A386official/slopgate@v1
id: slopgate
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Block merge if flagged
if: steps.slopgate.outputs.verdict == 'block'
run: exit 1pull_request event
|
v
+-----------+
| index.ts | Entry point: load config, fetch PR data
+-----------+
|
v
+-------------------+
| checks/behavioral | velocity, abandonment, shotgun, new_account
| checks/content | placeholder, hallucinated_import, docstring_inflation, copy_paste
| checks/patterns | generic_description, oversized_diff, unrelated_changes, formatting_only
+-------------------+
|
v
+-------------+
| scoring.ts | Weighted average -> verdict
+-------------+
|
v
+-------------+
| respond.ts | Labels, comments, review requests, auto-close
+-------------+
| Scenario | Without | With SlopGate |
|---|---|---|
| Bot opens 20 identical PRs across repos | You review and close each one manually | Detected by velocity + shotgun checks, auto-labeled or auto-closed |
| New account submits 800-line PR titled "fix bug" | Lands in your review queue with no context | Flagged: generic description + oversized diff + new account signals |
| PR adds 50 empty functions with TODO stubs | Looks like progress at a glance | Caught by placeholder check, detailed findings posted |
| PR imports packages that don't exist in your project | Breaks CI, wastes reviewer time investigating | Hallucinated import check identifies unknown dependencies immediately |
| PR is 90% comment padding with 3 lines of actual code | Inflates the diff to look substantial | Docstring inflation check exposes the real code-to-comment ratio |
# Install dependencies
npm install
# Run tests
npm test
# Type check
npx tsc --noEmit
# Build
npm run buildSee CONTRIBUTING.md for guidelines.
MIT -- A386official