I review a lot of PRs at work. Over the last year, I started noticing patterns in AI-generated code that kept showing up. Same five or six things, every time.
Empty catch blocks. as any sprinkled everywhere. Comments that just restate what the code does. Hardcoded API keys. except: pass in Python. The code works, it passes tests, but it's the kind of stuff you'd flag in a review and ask someone to fix.
The numbers back this up. CodeRabbit found AI-generated PRs have 1.7x more issues than human PRs. Veracode says 45% of AI code samples contain security vulnerabilities.
ESLint catches syntax issues. But nobody's catching the behavioral patterns that AI tools leave behind. So I built one.
vibecheck
24 rules across JS/TS and Python. Zero config. Runs offline. Regex-based, so it's fast.
npx @yuvrajangadsingh/vibecheck .
src/api/routes.ts
12:5 error no-hardcoded-secrets Hardcoded secret detected
45:3 error no-empty-catch Empty catch block swallows errors
89:1 warn no-console-pollution console.log left in production code
src/utils/db.ts
34:5 error no-sql-concat SQL query built with string concatenation
4 problems (3 errors, 1 warning)
2 files with issues out of 47 scanned (0.8s)
No API keys, no cloud, no LLM calls. It's regex rules that match the patterns AI tools tend to produce.
What it actually catches
Here's the stuff I kept flagging in code reviews:
The silent failure:
try:
response = api.fetch(user_id)
except:
pass
Your API call fails and nobody ever knows. vibecheck flags no-bare-except and no-pass-except.
The "I'll type it later":
const data = response.json() as any;
AI tools love as any. It shuts up the type checker but defeats the entire point of TypeScript. vibecheck flags no-ts-any.
The useless comment:
// initialize the counter
let counter = 0;
Your linter doesn't care about this. vibecheck does. no-obvious-comments catches comments that just repeat what the code already says.
The hardcoded key:
const API_KEY = "sk-proj-abc123def456";
This one's obvious but it keeps happening. no-hardcoded-secrets matches common API key patterns.
Diff mode
This is the part I use most. Instead of scanning your entire codebase, scan only the lines you just changed:
vibecheck --staged .
Drop it in a pre-commit hook and it only checks what you're about to commit:
# .git/hooks/pre-commit
npx @yuvrajangadsingh/vibecheck --staged .
In CI, it runs on PR diffs so you're not drowning in warnings from old code.
"Isn't this just ESLint?"
No. ESLint catches syntax and style. vibecheck catches the patterns that come from how AI tools generate code. Your linter won't flag a catch block that only does console.error(err) without rethrowing. It won't flag # type: ignore without a specific error code. It won't flag a function that's 120 lines long because the AI didn't know when to stop.
They're complementary. Run both.
Install
# no install needed
npx @yuvrajangadsingh/vibecheck .
# or install globally
npm install -g @yuvrajangadsingh/vibecheck
# standalone binary (no Node required)
curl -fsSL https://github.com/yuvrajangadsingh/vibecheck/releases/latest/download/vibecheck-darwin-arm64 -o vibecheck
chmod +x vibecheck
GitHub: github.com/yuvrajangadsingh/vibecheck
Open to feedback on what rules to add next. If you keep flagging the same thing in AI-generated PRs, I probably want to hear about it.
Top comments (0)