The guard passed every unit test, and then I asked a fresh Claude Code session to write a file that should have tripped it. The Write succeeded. No hook fired.
What the guard was for
On July 21 at about 3:28 PM, a session ran a leading-wildcard LIKE '%...' scan serially over six cache tables in our shared production database. The worst table took 139 seconds and 835K logical reads. The app’s own cache DELETEs hit the 30-second ODBC timeout, and every team page errored for about five minutes. A “read-only probe” still takes locks and I/O, and all our clones point at one live database.
So we wrote a PreToolUse hook. It reads the raw JSON payload on stdin and blocks when two things appear together: a prod-DB connection marker (our credential env vars, a DSN string, pyodbc, sqlcmd, Invoke-Sqlcmd) and a LIKE followed by a quoted string starting with %. It has no dependencies, just grep -E on the payload, so it runs the same for everyone from the checked-in repo. The registration matches Bash|Write|Edit|MultiEdit.
It started life on one operator’s desktop. This week we shipped it from all three repos. One of them, the Node API that talks to the same live database, had no .claude directory at all.
Two probes that walked past it
The unit tests fed the hook literal SQL like WHERE cacheKey LIKE '%foo'. That is the shape the regex was written against, so of course it passed.
Live testing meant prompting fresh sessions to write a script with the wildcard first in the pattern, then report verbatim whether the Write was blocked. Two probes:
- Node with
mssql. The script did.input('pattern', sql.NVarChar, '%session')and then.query('... WHERE cacheKey LIKE @pattern'). TheLIKEis followed by@pattern, not a quote, so the scan regex never sees it. Worse, the connection marker never matched either. That API readsDB_SERVER,DB_NAMEand so on, and none of my markers listed those. The port would have shipped a hook that could not fire in that repo. - PowerShell with
Invoke-Sqlcmd. The script built$pattern = '%' + $KeySuffixand putLIKE $(pattern)in the query with-Variable @("pattern='$pattern'"). The Write went through.
Chasing sigils
My first move after probe 2 was to fix the regex. I had already handled the bare-literal case, so I widened it to parameterized ones: LIKE followed by a placeholder sigil. I wrote that regex into a replay harness that rebuilds the real PreToolUse payload for the Write and pipes it through the hook, and I ran it against the PowerShell payload:
conn: MATCHscan: no matchparam_like: no matchwildcard_literal: MATCHparam_like was LIKE[[:space:]]*[@:?\$][A-Za-z0-9_]. It covers @name, :name, ? and $name. sqlcmd variable substitution is $(name), a dollar sign followed by a paren, so it slid straight past. I had spent a cycle building the second special case for a guard whose whole job was to stop chasing special cases. Every driver spells its placeholder differently. That is the wrong shape for the match, and I would have kept finding new sigils in live tests, one production near-miss at a time.
The last line of that output was the useful one. wildcard_literal was N?['"][[:space:]]*%, a quote followed by %, with no LIKE in front. It matched the PowerShell payload, because the leading wildcard exists as a literal ('%') in the script whether or not the SQL text ever contains it. It also matches the Node payload’s '%session'.
Matching where the wildcard is born
The match is now shape-independent. The condition is a connection marker plus a string literal that begins with %, anywhere in the payload, with no requirement that the literal sit next to LIKE. The wildcard has to originate somewhere as a quoted value, even when the SQL only ever sees a placeholder. Matching that origin covers @pattern, $(pattern), :pattern and whatever the next driver invents.
The connection markers gained the Node/mssql shape, so the guard can fire in the API repo. Then I re-ran both probes in fresh sessions and did not stop at the unit tests. Both were blocked.
What it still does not catch
The hook’s header already admits a known limit. CHARINDEX and PATINDEX over a whole table wedge prod just as well, but they have heavy legitimate bounded use, and matching them would false-positive constantly. Markdown files are exempt so incident write-ups can quote the pattern. It is a nudge with teeth, and the wildcard-literal match is broader, so it will occasionally fire on a harmless '%', which I accept.
A unit test written by the person who wrote the regex checks the regex against the author’s imagination. Both bypasses came from an agent writing code the way agents actually write it, and only a payload replay from a real session caught them.