AI coding assistants are exceptionally capable at generating syntactically valid code that compiles cleanly and passes shallow unit tests. Yet when teams deploy AI-generated JavaScript and TypeScript code to production, a distinct failure pattern emerges: silent bugs.
Unlike traditional syntax errors or type mismatches that break the build, silent bugs operate in the background—swallowing errors, leaking unresolved promises, or causing race conditions under concurrent load.
Here is an analysis of the five most frequent anti-patterns discovered across hundreds of AI-generated repositories, and how deterministic AST analysis solves them.
1. The Floating Promise Dilemma
Large language models are trained on billions of code tokens where the majority of code focuses on standard synchronous flow or simple happy-path scripts. When generating asynchronous pipelines, models routinely invoke promise-returning functions without prefixing them with await or chaining a .catch() handler:
// ❌ Floating promise generated by AI assistant
async function handleUserSignup(req: Request, res: Response) {
const user = await db.users.create(req.body);
sendConfirmationEmail(user.email); // Returns Promise<void>, never awaited!
res.status(201).json({ success: true });
}Why this is dangerous: In Node.js, if sendConfirmationEmail rejects (due to a mail server timeout or network hiccup), the rejection event goes unhandled. In modern Node.js versions, unhandled rejections trigger process warnings or termination. Worse, the API response has already been sent, meaning the failure is entirely invisible to the client.
The AST Solution: AI Guard’s no-floating-promise rule inspects CallExpression nodes within function scopes. If the callee is known or inferred to return a Promise and sits as an unassigned ExpressionStatement without await or .catch(), it flags the violation immediately.
2. The Async Array Callback Trap
Another classic AI pattern occurs when iterating over arrays using functional methods like .map() or .forEach():
// ❌ AI-generated attempt to process array items concurrently
async function updateAllInventory(items: Item[]) {
items.forEach(async (item) => {
await inventoryService.deductStock(item.id, item.qty);
});
console.log('All inventory updated!'); // Executes immediately before deductions finish!
}Array.prototype.forEach does not await promises returned by its callback. The loop kicks off promises into the microtask queue and exits synchronously. The function continues, falsely believing all inventory was deducted.
When using .map(async ...), the returned array is an array of Promises (Promise<void>[]), not the resolved data.
The Fix: Use Promise.all(items.map(...)) or a for...of loop. AI Guard’s no-async-array-callback rule detects when an async function expression or arrow function is supplied to iterative array prototypes.
3. Empty and Broad Catch Blocks
When prompt engineering asks an AI assistant to "ensure error resilience" or "handle all exceptions gracefully," the model often produces defensive boilerplate like this:
// ❌ Exception swallowing
try {
await paymentGateway.charge(order.total);
} catch (e) {
// Silent fallback: returns false without logging the error
return false;
}When a database connection dies, an API token expires, or a critical payment failure occurs, the catch block catches everything—even fatal bugs—and silently drops them. Months later, debugging becomes a nightmare because there is no stack trace or alert.
AI Guard’s no-empty-catch and no-broad-exception enforce that errors are properly inspected, logged, or re-thrown.
4. Raw SQL and Query Concatenation
AI models often prioritize brevity. When constructing database queries in Express or Node.js handlers, they frequently use template literals instead of parameterized queries:
// ❌ SQL injection risk in AI-suggested endpoint
const query = `SELECT * FROM accounts WHERE organization_id = '${orgId}' AND status = 'active'`;
const accounts = await db.query(query);Even if the developer specified an ORM in the prompt, AI assistants may revert to string interpolation when asked to write a custom filter or aggregation. AI Guard’s no-sql-string-concat flags any binary expression or template literal concatenated into database query method calls.
5. Dead Scaffolding Branches
AI models frequently leave conditional placeholders from earlier iterations of prompt conversations:
if (true) {
// AI left temporary bypass
return mockUserData();
}Or tautological comparisons like if (status === status). AI Guard’s no-dead-branch traverses logical and conditional AST expressions to eliminate dead code before code review.
Conclusion
AI tools accelerate development dramatically, but probabilistic models are fundamentally unsuited to guarantee determinism. By embedding AST-based guardrails into your ESLint configuration and GitHub Actions pipeline, your team gets the productivity of AI assistance without the silent reliability failures.
