Vibe coding with AI runs into a lack of software engineering fundamentals

- Reading
- 7 min
- Code
- 2
An article published on Habr details five technical layers, from the operating system kernel to distributed networks, that code generated by tools like Cursor, Claude Code, and GitHub Copilot tends to ignore.
From code generation to the system in production
An article published on November 8, 2026 on Habr, a Russian-language tech community site, signed by user kimchi621, proposes a map of the technical layers that separate "those who operate a prompt" from those who actually design systems. The text starts from a direct diagnosis: tools like Cursor, Claude Code, and GitHub Copilot have drastically reduced the cost of generating syntactically correct code, but that is not the same as software engineering. For pet projects, hackathon prototypes, and quick validation of a product hypothesis, the speed gain is real. The problem starts when that same code faces physical memory, database concurrency, and network failure in production.
The neural network generates code, but the architecture, fault-tolerance invariants, and data models remain the human's responsibility.
kimchi621, author of the article on HabrНейросеть генерирует код, но архитектуру, инварианты отказоустойчивости и модели данных за нее обязан держать человек.
This angle matters to anyone working in Brazil with any stack that today uses an AI copilot on a daily basis, from the junior developer who learned to program with an open chat window to the team that sped up delivery with assisted generation. The article is not against vibe coding; it is against treating the model's output as a final product without review from someone who understands what happens underneath the code.
Where the event loop stalls: memory, I/O, and memory pages
The first blind spot pointed out is I/O handling in single-threaded runtimes, as is the case with Node.js and asyncio/FastAPI in Python. The article shows a common pattern in AI-generated code: synchronous file reading inside an HTTP handler, with no size limit and no descriptor closing in case of error.
app.post('/import', async (req, res) => {
const data = fs.readFileSync(req.body.filePath); // blocks the OS thread
const parsed = JSON.parse(data); // stalls the event loop for seconds
for (const item of parsed) {
fs.createReadStream(item.file); // descriptor doesn't close on error
}
res.json({ status: 'ok' });
});With a file of a few kilobytes in development, this runs smoothly. In production, a 500 MB upload stalls the entire event loop: the load balancer stops receiving a health-check response, and thousands of open streams bring down the process with an EMFILE: too many open files error.
The article distinguishes three I/O models that explain why: classic blocking I/O (one thread per connection, expensive in memory), multiplexing via epoll/kqueue (one worker handles thousands of sockets, used by Nginx, Redis, and Node.js itself), and io_uring, the newer Linux kernel mechanism that exchanges data with the process through shared memory queues, reducing the need for system calls on every operation.
The text also notes that memory in runtimes with garbage collection (JavaScript, Python, Go, Java) is not immune to leaks: a global cache without TTL, an event listener that's never removed (emitter.on), and a closure stuck in a long-lived scope keep objects alive forever. The result is growing RSS, disk swap, and ultimately the process being killed by Linux's OOM Killer.
Data without fundamentals turns into losses: transactions and indexes
The article's second block is about literal money. The example is a balance withdrawal written the way an AI model tends to generate it: read the balance, check if it's sufficient, write the new value, in three separate steps.
async function withdrawMoney(userId: string, amount: number) {
const user = await db.user.findUnique({ where: { id: userId } });
if (user.balance >= amount) {
const newBalance = user.balance - amount; // another request can read the same balance here
await db.user.update({ where: { id: userId }, data: { balance: newBalance } });
}
}Two simultaneous requests to withdraw R$ 100 from a balance of R$ 100 read the same value, both pass the check, and both write zero: the user withdrew R$ 200 with only R$ 100 available. This is the classic Lost Update anomaly. The article lists three concrete fixes, each with a different concurrency cost:
- Pessimistic lock:
SELECT balance FROM accounts WHERE id = 1 FOR UPDATElocks the row until the transaction finishes. - Optimistic lock: a version column in
UPDATE ... WHERE id = 1 AND version = 5fails silently (zero rows affected) if another transaction has already touched the row, and the application decides whether to retry. - Atomic SQL operation:
UPDATE accounts SET balance = balance - 100 WHERE id = 1 AND balance >= 100solves the simplest case without an extra round-trip.
The same block covers two other recurring errors in AI-generated code: the N+1 query problem in ORMs (fetching 100 posts and then running a SELECT per author inside the loop, instead of JOIN/include or DataLoader), and using float/double for money, which breaks due to binary floating-point representation (0.1 + 0.2 === 0.30000000000000004 in JavaScript).
The recommendation is to use an integer in the smallest unit (cents) with BigInt, or DECIMAL/NUMERIC in the database. On indexes, the article reinforces that B-Tree is the standard for equality, range, and ordering; hash indexes only work for exact equality; and GIN is the way to go for full-text search and JSONB fields, though each extra index adds write cost on INSERT/UPDATE/DELETE.
Networks will fail: timeouts, idempotency, and circuit breakers
The third layer discussed is networking and distributed systems, drawing on the eight "fallacies of distributed computing" formulated by Peter Deutsch and James Gosling in the 1990s (the network is reliable, latency is zero, bandwidth is infinite, among other assumptions that every real system disproves). The article directly cites Michael Nygard (author of Release It!) as a reference for three resilience patterns:
- Mandatory timeout on every network call, because a stuck dependency with no timeout consumes every free thread in the pool and brings down the entire system in a cascade.
- Exponential backoff with jitter: retrying a failed call immediately generates a DDoS against the very service that's trying to recover; the delay needs to grow exponentially with added random noise.
- Circuit breaker with three states (closed, open, half-open), which cuts calls to a dependency already identified as down instead of insisting.
The article's code example shows a payment fetch with no timeout and no protection against duplication: if the call fails due to timeout but the gateway has already debited the amount, a naive retry charges twice. The solution discussed is the idempotency key: the client generates a unique identifier (Idempotency-Key in the HTTP header), the server saves that identifier along with the operation's result, and a second call with the same key receives the already-recorded response instead of executing the logic again.
The article further details protection against races between the retries themselves: the key enters the database with status IN_PROGRESS before the business logic runs, and a duplicate call that arrives before the first one finishes receives 409 Conflict or waits for completion.
What to study beyond the prompt
The article closes by citing four references as reading material for moving beyond vibe coding: Designing Data-Intensive Applications, by Martin Kleppmann (data, transactions, distribution); Release It!, by Michael Nygard (production resilience); Google's Site Reliability Engineering material (operations and observability); and Computer Systems: A Programmer's Perspective, by Randal Bryant and David O'Hallaron (hardware, OS, memory). None of these books is recent, nor do they talk about generative AI, and that is exactly the article's point: what changes with vibe coding is the speed of producing the first version, not the body of knowledge needed for it to withstand real traffic.
For developers in Brazil who use an AI copilot on a daily basis, the practical takeaway is to decide where human review is non-negotiable before any deploy: every network call has a timeout and idempotency, every financial operation is atomic in the database (not in three steps in the application code), and every single-threaded process has a clear separation between I/O and heavy computation. The original article, in Russian, is available on Habr and details even more examples of each layer, including the CAP theorem and the Transactional Outbox pattern for reliable messaging.
Translated from the Brazilian Portuguese original · Read the original
Harness, DeepSeek's AI agent tool, hits 1.54 million users per day
Launched in August, DeepSeek's agent framework already processes 92 trillion tokens per day and is building a plugin ecosystem to compete with OpenAI's Codex and Anthropic's Claude Code.





