Cogloom · Practical AI & coding

AI code guardrails: nine checks that catch what the model got wrong

Nine runnable commands, one pre-commit hook, and the self-check that proves the hook is live

AI writes plausible code fast. Plausible and correct diverge in specific, repeatable places, and they are the same places every time: invented dependencies, silently swallowed errors, deleted code you didn't notice, tests that can't fail, destructive commands in generated scripts, and scope creep into files you never mentioned.

You do not need to read every line the model writes. You need nine checks aimed at those six failure modes. Total runtime: under a minute. Everything on this page is the whole thing — no gate, no download, no sign-up to see the rest.

Prerequisites: git, and ripgrep (rg) — install via winget install BurntSushi.ripgrep.MSVC, brew install ripgrep, or apt install ripgrep. Every rg command below has a PowerShell fallback noted where the syntax differs.

Want the whole thing in your inbox? We'll email you this full guardrails pass — all nine commands, the hook, and the 60-second loop — as one plain-text message you can keep next to your terminal. No download, no account, no drip sequence.

Email me the full guardrails →

Check 1 — Review the diff, never the chat

The chat transcript is a summary written by the thing you are checking. The diff is what actually happened.

git add -A -N          # include new untracked files in the diff
git diff --stat        # what changed, how much
git diff               # the actual lines

-N registers untracked files as intent-to-add so git diff shows them. Without it, an entire new file the model created is invisible to your review.

If the --stat line count is wildly larger than the change you asked for, stop here and go to Check 9.

Check 2 — Verify every dependency it added

Models invent package names. The name looks right, it isn't real, and attackers register the plausible ones — a supply-chain attack that works precisely because the name was generated rather than chosen.

git diff -- package.json package-lock.json requirements.txt pyproject.toml go.mod

For each added package, confirm it exists and is what you think:

npm view <package> versions --json | tail -5   # last published versions
npm view <package> repository.url              # does it point somewhere real?
pip index versions <package>                   # Python equivalent

Red flags: first published within the last few weeks, no repository URL, download counts near zero, or a name one character off a package you recognize.

Rule: if the model added a dependency to do something the standard library already does, delete the dependency.

Check 3 — Secrets

git diff --cached | rg -n "(?i)(api[_-]?key|secret|passwd|password|token|bearer |aws_(access|secret)|BEGIN (RSA|EC|OPENSSH|PRIVATE) )"

PowerShell:

git diff --cached | Select-String -Pattern 'api[_-]?key|secret|password|token|BEGIN .*PRIVATE'

Also check that a real key didn't get baked into an example:

rg -n "sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}" --hidden -g '!.git'

If a real secret was ever committed, rotate it. Removing it from the file does not remove it from git history, and rewriting history does not un-publish anything already pushed.

Check 4 — Deletions

The dangerous edit is not the code added. It is the code quietly removed to make an error go away.

git diff --cached --numstat | awk '$2 > 30 {print $2" lines removed from "$3}'
git diff --diff-filter=D --name-only        # files deleted outright
git diff -U0 | rg "^-" | rg -v "^---"       # every removed line, no context noise

Read the removed lines. Every one. This check catches more real bugs than the other eight combined.

Check 5 — Destructive commands in generated scripts

Anything the model wrote that will be executed — a shell script, a migration, a CI step, a cleanup task:

rg -n "rm -rf|DROP (TABLE|DATABASE)|TRUNCATE|DELETE FROM \w+\s*;|shutil\.rmtree|--force|-f\b|git push --force|chmod 777|curl [^|]*\| *(ba)?sh" \
   --glob '!node_modules'

Two rules that cost nothing:

Check 6 — Swallowed errors

rg -n "except\s*:\s*$|except Exception:\s*pass|catch\s*\([^)]*\)\s*\{\s*\}|catch\s*\{\s*\}|\.catch\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)|_ = err|_,\s*_\s*=" \
   --glob '!node_modules' --glob '!*.min.js'

An empty catch block is how a model makes a failing test pass. The bug is still there; you have only removed the alarm.

Check 7 — Prove the tests can fail

A test suite written by the same process that wrote the code is not independent evidence. Verify it can fail:

rg -n "expect\(true\)\.toBe\(true\)|assert True|assertTrue\(True\)|it\.skip|test\.skip|@pytest.mark.skip|xit\(" tests/ test/ spec/ 2>/dev/null

Then the real check — break the code on purpose and confirm the suite notices:

git stash                 # set the new code aside
<run your test command>   # should FAIL — the feature isn't there
git stash pop             # bring it back
<run your test command>   # should PASS

If step two passes without the implementation, the test is asserting nothing. Delete it; it is worse than no test because it reports safety.

Check 8 — Pin what it added

Generated setup instructions default to floating versions. Lock them.

npm ci                     # installs exactly the lockfile — never `npm install` in CI
git diff -- package-lock.json | head -40    # confirm the lockfile actually changed
pip install -r requirements.txt             # with pinned ==versions, or use uv/pip-tools

If the model edited package.json but not package-lock.json, the install it "tested" is not the install you will ship.

Check 9 — Scope

git diff --name-only
git diff --name-only | wc -l

Compare against the files you actually asked it to touch. Anything else is uninstructed change: a reformat, a "while I was here" refactor, a config tweak. Strip it:

git restore -p <unrelated-file>     # interactively drop hunks
git checkout -- <unrelated-file>    # drop the file's changes entirely

Uninstructed changes are where regressions hide, because nobody reviews a file they didn't expect to see.


Install the hook (30 seconds)

Save as .git/hooks/pre-commit and chmod +x .git/hooks/pre-commit. It runs Checks 3, 5, and 6 on every commit and blocks on a hit.

#!/usr/bin/env bash
# three greps beat a scanning service you have to configure.
set -euo pipefail
fail=0
hit() { printf '\033[31mBLOCKED: %s\033[0m\n' "$1"; fail=1; }

d=$(git diff --cached)

echo "$d" | grep -Eqi '(api[_-]?key|aws_secret|BEGIN (RSA|EC|OPENSSH|PRIVATE) )' \
  && hit "possible secret in staged diff"
echo "$d" | grep -Eq 'sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}' \
  && hit "live-looking credential in staged diff"
echo "$d" | grep -Eq '^\+.*(rm -rf /|DROP (TABLE|DATABASE)|TRUNCATE |shutil\.rmtree)' \
  && hit "destructive command added"
echo "$d" | grep -Eq '^\+.*(except Exception: *pass|except: *$|catch *\{ *\})' \
  && hit "empty exception handler added"

[ "$fail" -eq 0 ] || { echo "Override with: git commit --no-verify"; exit 1; }

Verify the hook works before trusting it

One runnable self-check. A guardrail you have never seen fire is a guardrail you are guessing about:

echo 'AWS_SECRET=AKIAIOSFODNN7EXAMPLE' >> /tmp/probe.txt && cp /tmp/probe.txt ./probe.txt
git add probe.txt && git commit -m probe    # must be BLOCKED
git reset HEAD probe.txt && rm probe.txt

If that commit succeeds, the hook is not executable. chmod +x .git/hooks/pre-commit and retry.


The 60-second loop

Run this after every AI-generated change, before you commit:

1. git add -A -N && git diff --stat      # scope sane?
2. git diff -U0 | rg "^-" | rg -v "^---"  # what disappeared?
3. deps added → npm view each one
4. rg for secrets / rm -rf / empty catch
5. git stash → tests fail? → stash pop → tests pass?
6. run the thing yourself, once, by hand

Step 6 is not optional. Every check above is static; none of them prove the program does what you wanted.

When to throw it away

Hard rule, and it will save you more time than any of the checks: if the model has been wrong about the same thing twice, stop iterating. A third attempt from the same context inherits the same wrong assumption. Either restate the problem from scratch in a clean session with the constraint that was missing, or write the twelve lines yourself.

The cost of AI-assisted code is not generation. It's review. Nine checks and a hook keep that cost bounded.

That's all nine, free, above. If you'd rather have it as one message in your inbox — the commands, the hook, and the loop, in plain text you can paste from — ask and we'll send it.

Email me the full guardrails →