I let Claude Code commit directly to my repositories. I don't review the diffs. I didn't think I needed to, because I built a deterministic verify gate — a script that lints, typechecks, builds, and runs tests. If the gate goes green, the PR merges automatically.
I trusted that gate implicitly. Until I actually sat down and asked the one question that matters: If an agent pushes completely broken code right now, will this gate actually go red?
Not "is the script configured?" Not "does the file exist?"
Will it actually fail?
Turns out, for three out of my four repos, the answer was an emphatic no. The gate was returning GREEN
without running a single line of code.
My gate script is repo-agnostic. It looks up the repository name in a policy JSON file (gates.<repoName>
) and falls back to a default if it doesn't find one.
Here is the PowerShell logic driving the whole operation:
$steps = $policy.gates.$RepoName
if ($null -eq $steps) { $steps = $policy.gates.default }
if (-not $steps -or $steps.Count -eq 0) {
Write-Log "GREEN (no gate steps configured for '$RepoName')"
exit 0 # <-- Unconditional green
}
And here is my config file:
"gates": {
"app": [
"npm run lint",
"npm run test",
"<design-gate script>"
],
"default": []
}
Look at that exit code. If a repo doesn't have an explicit entry in the JSON file, it falls through to default: []
. The script sees zero steps, prints GREEN
, and exits with 0
.
The runner sees exit code 0
and instantly merges the code. I built a system where having no tests configured is structurally identical to passing all tests.
When I audited all four repos the runner is allowed to touch, the reality was pretty grim:
app
(The Runner itself):npm run lint
runs, but tsconfig
explicitly excludes the runner/
directory — meaning build
isn't in the gate at all, so client/server boundary breaks ship freely. Vitest runs 28 test files, but the glob pattern completely misses *.test.tsx
files.product
(Next.js app):tsc --noEmit
, vitest
(10 suites), and next build
ready to go in package.json
. None of them were wired into the gate config. It had 0% coverage purely because of lazy config debt.notes
(Markdown vault):portfolio
(Vite site):The real bug here isn't missing npm scripts. The structural flaw is that an empty gate defaults to success.
To fix this properly:
$steps.Count -eq 0
, the script must exit non-zero or return an explicit UNGATED
status. A missing key should break the build, not bypass it.product
repo was fixed with a single line in policy.json
. npm run build
to the gate costs execution time, but it's the only way to catch real deployment breakers.tsconfig
and fix the Vitest globs before someone writes a .tsx
test that never actually gets executed.If you run AI agents against your codebase without reading the diffs, go break a file on purpose and run your gate. If it doesn't yell at you, you don't have a safety gate — you just have a script that automatically approves bad code.
I'm Andréas — full-stack dev, CTO at a B2B SaaS, building my own agent tooling. Portfolio: https://andreas-bodin.vercel.app