Using with LLMs
The MCP server only helps if the agent calls it before
shipping a database change, and agents often don’t — they decide an edit isn’t
“database work” and skip it. Adding one column to a SELECT can turn a fast
query into a full table scan, and by the time CI
catches it you’re a push and a pipeline run behind.
The hooks below make an agent check its data-layer edits locally. Examples are for Claude Code; adapt the triggers to your host.
Two things make edits slip past an agent’s own judgement:
- Nothing forces a check on the data layer. UI edits often sit behind a review step; a query or migration edit usually fires nothing.
- ORM queries aren’t in a
db/folder. They sit inline in route and service files, so a path-based guard misses them. Match on what the edit contains.
Before you start
Section titled “Before you start”Register the MCP server and install the companion skill. The skill shifts the agent’s default toward using the tools; the hooks are the backup for when it doesn’t.
Guardrail 1 — a reminder hook that reads content
Section titled “Guardrail 1 — a reminder hook that reads content”A PostToolUse hook that inspects what was written and, if it looks like
database code, reminds the agent to validate it. Create
.claude/hooks/qd-db-nudge.sh:
#!/bin/bash# Nudge the agent to validate data-layer edits against the Query Doctor MCP.# Content-based: fires on inline ORM/SQL calls, not just files under a db/ folder.
file_path=$(jq -r '.tool_input.file_path // ""')# Reconstruct the text that was written, regardless of the edit tool used.content=$(jq -r ' .tool_input.content // .tool_input.new_string // ([.tool_input.edits[]?.new_string] | join("\n")) // ""')
# Only consider source files.case "$file_path" in *.ts | *.tsx | *.js | *.jsx | *.sql | *.py | *.rb | *.go | *.cs) ;; *) exit 0 ;;esac
# Content signals: ORM query builders, raw SQL tags, and migration/schema paths.query_signal='\.select\(|\.insert\(|\.update\(|\.delete\(|\bdb\.|sql`|CREATE (TABLE|INDEX)|ALTER TABLE'path_signal='migration|schema|/db/'
if echo "$content" | grep -qiE "$query_signal" \ || echo "$file_path" | grep -qiE "$path_signal"; then cat >&2 <<'EOF'Query Doctor: this edit touches the data layer. Before you finish, validate itagainst the Query Doctor MCP: - analyze_query / explain_query — check the plan for seq scans and bad sorts - recommend_indexes — confirm supporting indexes exist for ORDER BY / WHERE / JOINEven projection-only changes (adding a column to a SELECT) can change the plan.After the change lands in CI, check the latest CI run before calling it done.EOF exit 2fiexit 0Register it in .claude/settings.json:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": ".claude/hooks/qd-db-nudge.sh" } ] } ] }}chmod +x the script and restart the agent. Exit code 2 surfaces the message
to the agent; it doesn’t block the edit.
Tune the signals to over-fire. A false alarm costs one cheap analyze_query
call; a miss costs a CI round-trip.
Guardrail 2 — lock down migration folders
Section titled “Guardrail 2 — lock down migration folders”Agents should regenerate migrations through the ORM, not hand-write the SQL — a
hand-edited migration breaks the ORM’s snapshot chain. Block direct edits with a
PreToolUse hook (.claude/hooks/block-manual-migrations.sh, Drizzle shown):
#!/bin/bashfile_path=$(jq -r '.tool_input.file_path // ""')if echo "$file_path" | grep -qE 'drizzle/'; then echo "Blocked: do not hand-edit generated migrations. Modify the schema, then run your ORM's generate step (e.g. drizzle-kit generate)." >&2 exit 2fiexit 0{ "hooks": { "PreToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": ".claude/hooks/block-manual-migrations.sh" } ] } ] }}Point it at wherever your ORM writes migrations.
Guardrail 3 — a final check before “done”
Section titled “Guardrail 3 — a final check before “done””A backstop for when the per-edit reminder is missed: scan the whole diff at the
end of a task. Add this to a completion checklist or /finish command:
# Run at the end of a task, before declaring the work done.changed=$(git diff --name-only origin/staging... )
if echo "$changed" | grep -qE '\.(ts|tsx|js|jsx|sql|py|rb|go|cs)$'; then # Look at the actual added lines, not just filenames — inline queries count. if git diff origin/staging... \ | grep -E '^\+' \ | grep -qiE '\.select\(|\.insert\(|\.update\(|\.delete\(|\bdb\.|sql`|CREATE (TABLE|INDEX)|ALTER TABLE'; then echo "Data-layer changes detected in this diff." echo "Validate them with the Query Doctor MCP (analyze_query / recommend_indexes)" echo "and check the latest CI run before declaring the work complete." fifiFurther reading
Section titled “Further reading”- MCP Server — install the server and companion skill.
- CI Quickstart — the CI check these run ahead of.
- Source Code Mapping — tracing a captured query back to the line that wrote it.