You write code with Cursor. It looks fine. You ship it.
Three days later: "why does my app keep losing user data?"
You trace it back to this:
def save_user(user_id: str, data: dict) -> dict:
validated = validate_schema(data)
sanitized = sanitize_input(validated)
log.info(f"Saving user {user_id}")
return {"status": "saved", "user_id": user_id}
Pylint: ✅ PASS
Mypy: ✅ PASS
Flake8: ✅ PASS
Every tool says it's fine. It's not fine. There's no INSERT anywhere. The function is named save_user and saves nothing.
Why AI generates this
Language models are trained on code where save_user functions validate input, call a DB function, and return success. When the model generates one, it produces the surrounding pattern — but sometimes drops the actual write. The result looks correct, behaves correctly in unit tests, and silently loses data in production.
This is the MISSING_WRITE pattern. Standard static analysis tools don't catch it because it's not a syntax error or a known CVE. It's a semantic pattern specific to LLM-generated code.
The fix loop
Step 1: Scan
curl -X POST https://pleasing-transformation-production-90c2.up.railway.app/v1/scan \
-H "X-API-Key: vg_free_test" \
-F "file=@your_file.py"
Step 2: Get the report
{
"passed": false,
"issues": [
{
"kind": "MISSING_WRITE",
"severity": "BLOCK",
"line": 1,
"detail": "save_user() contains no INSERT/UPDATE — data is never persisted"
}
]
}
Step 3: Paste into Cursor
Fix this issue in save_user():
MISSING_WRITE (line 1): save_user() contains no INSERT/UPDATE — data is never persisted
Step 4: Cursor fixes it
def save_user(user_id: str, data: dict) -> dict:
validated = validate_schema(data)
sanitized = sanitize_input(validated)
conn = get_db()
conn.execute(
"INSERT INTO users (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data=excluded.data",
(user_id, json.dumps(sanitized))
)
conn.commit()
log.info(f"Saving user {user_id}")
return {"status": "saved", "user_id": user_id}
Step 5: Scan again → CLEAN ✅
Total time: under 30 seconds.
Other patterns in the same category
| Pattern | What it means |
|---|---|
FAKE_ASYNC |
async def with no await — blocking call in async context |
STUB_SKELETON |
Function returns True/{} for everything — logic was never written |
INPUT_OUTPUT_DISCONNECTED |
Parameters don't affect the return value |
DEAD_CALL_RESULT |
Module results ignored — check_inventory() returns False, order still "processes" |
All of these are patterns that appear specifically in AI-generated code. Standard linters miss them because they're syntactically valid.
Add to CI so you catch it before it ships
# .github/workflows/scan.yml
- uses: Moonsehwan/aina-vibeguard-action@v1
with:
api-key: ${{ secrets.VIBEGUARD_KEY }}
Free key during beta: vg_free_test
48 patterns, 9 languages (Python, JS, TS, Go, Java, Ruby, PHP, Kotlin, C/C++).
The point isn't to replace your AI coding tool. It's to close the loop — scan what the AI wrote, paste the report back in, let it fix itself.
Top comments (0)