I manage about ten SvelteKit repositories deployed on Cloudflare Workers, and leveraged Anthropic's Claude Code to do it. Generally speaking, AI coding assistance can be fast and capable, especially if you already know how to code, but precisely because they are so fast, they can be — if you're not careful — consistently wrong in ways that are hard to spot.
Not wrong as in "the code doesn't work." Wrong as in: it uses .parse() instead of .safeParse(), it interpolates variables into D1 SQL strings instead of using .bind(), it fires off database mutations without checking the result, it nests four levels of async logic inside a load function that should have been split into helpers. The code works. It passes TypeScript.
The problem is that if you add guidance to your CLAUDE.md file (or other coding agents' guide files) such as "always use safeParse()" and "never interpolate SQL", those are just suggestions, not constraints. The AI reads them, and might follow them, but also it might not. There is no compiler error when it forgets. The instruction is non-deterministic, so the compliance is non-deterministic too.
This post is about how I made compliance deterministic.
The Principle
In hydraulics, 'backpressure' is resistance applied to a flow to regulate it. In software, the equivalent is making bad patterns structurally impossible rather than merely discouraged.
A CLAUDE.md instruction is a sign on the pipe that says "please don't overflow." A lint rule is a pressure valve that rejects the bad flow automatically. The sign might be ignored; the valve cannot.
The goal: migrate every "always" and "never" statement out of documentation into something mechanical — a type, a lint rule, a test, or a structural check. What remains in CLAUDE.md should be context and intent — the why, not the what.
Three Linting Passes
Each linting tool has a different strength.
oxlint (~50ms) — Rust-based, checks ~200 universal JS/TS rules. Fast correctness pass.
ESLint + Svelte plugin — Understands .svelte files as a whole. Hosts custom backpressure rules:
| Rule | What it catches |
|---|---|
no-raw-html |
{@html expr} without sanitization |
no-binding-leak |
Returning platform.env.* from load functions |
no-schema-parse |
.parse() instead of .safeParse() on Zod |
no-silent-catch |
Empty catch blocks |
These encode exactly the "always" and "never" statements that used to live only in prose.
ast-grep — The newest and most interesting. Uses tree-sitter to match code by structure. Rules are declarative YAML:
id: n-plus-one-query-map
language: TypeScript
severity: warning
message: >-
Potential N+1 query: database call inside .map().
Use db.batch() or WHERE IN instead.
rule:
pattern: $ARR.map($$$ARGS)
has:
pattern: $DB.prepare($$$SQL)
stopBy: end
This catches something neither oxlint nor ESLint can express: a database query nested inside an array iteration. In a SvelteKit load function hitting Cloudflare D1, this is a performance disaster. The AI generates this regularly because it looks correct and works fine on small datasets.
Full ast-grep rule set:
| Rule | What it catches |
|---|---|
sql-injection-d1 |
Template literals in db.prepare()
|
n-plus-one-query-* |
DB calls inside .map(), .forEach(), for...of
|
unbounded-query-all |
.all() without LIMIT |
unchecked-db-run |
Fire-and-forget .run()
|
empty-catch-block |
Silent error swallowing |
Each corresponds to a mistake I found in AI-generated code that passed both oxlint and ESLint.
Tracking Upstream
Mechanical enforcement handles known patterns. But Svelte and Cloudflare ship constantly — SvelteKit has had 50+ releases since 5.0. The AI doesn't know about features released last week.
I built a "what's new" audit: a shell script fetches my SvelteKit patterns feed (a JSON Feed 1.1 endpoint with grep-friendly search signatures) and the Cloudflare changelog RSS. It filters CF entries to only the products each repo uses (by reading wrangler.jsonc bindings), then searches source code for legacy patterns.
Output: "repo x has 3 files still using writable() stores — $state class replacement available since Svelte 5.29."
When the audit discovers features not in the feed, it flags those as gaps. The feed stays current because the audit tells us when it's behind. The audit runs in about 10 seconds across all ten repos — it's a shell script, not an AI call.
Centralize, Then Distribute
All rules, configs, and workflows live in one .github repo. A sync script distributes to ten consumer repos. One sync-all.sh updates everything. No drift.
What It Catches
Since deploying, the ast-grep rules alone have flagged:
- Template literal SQL injection in load functions the AI generated when asked to "add a search filter"
- N+1 patterns where the AI helpfully awaited each item individually
- Unbounded
.all()queries that worked in development (5 rows) and would have timed out in production (50,000 rows) - Empty catch blocks where D1 errors vanished silently
None produced TypeScript errors. None were caught by oxlint or ESLint. All would have shipped.
Takeaways
Audit your
CLAUDE.mdor other guidance files for "always" and "never" statements. Each is a candidate for a lint rule or type constraint. Do that, then remove the prose.Linters have different strengths. oxlint (fast, shallow) + ESLint (framework-aware) + ast-grep (structural) at 50ms + 2s + 200ms is still faster than one human review.
Track upstream releases programmatically. A structured feed with search signatures turns "what's new?" into a deterministic scan.
Centralize, then distribute. Ten repos with synced configs means zero drift.
The AI writes code faster than you can review it. The answer isn't slower AI — it's making the codebase reject bad patterns automatically, immediately, and without judgment.
The systems described here are specific to SvelteKit on Cloudflare Workers, but the principle — mechanical enforcement over written instructions — applies to any AI-assisted codebase. My svelte patterns feed is public if you want to point your own tooling at it.
Originally published at cogley.jp
Rick Cogley is CEO of eSolia Inc., providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.
Top comments (0)