Generative AI promised to eliminate one of the biggest sources of friction in software engineering: Writing code. In many ways, it has delivered. Today, an AI coding agent can implement features in minutes that might have taken an engineer an hour. It can generate tests, refactor code, write documentation, navigate large repositories, and even execute development tasks autonomously. But the real question is whether the code produced by generative AI is actually good.
Using AI makes us feel like we're moving faster because code is generated almost instantly. Yet moments later, we're staring at a 2,000-line diff, trying to understand why the agent refactored unrelated files, whether the new retry logic is safe, or which seemingly harmless change introduced a regression.
Code generation has become remarkably inexpensive. Engineering hasn't.
This is what is called the AI productivity paradox. AI has dramatically reduced the cost of producing code, but the work that truly defines software engineering—understanding, reviewing, validating, integrating, and maintaining that code—has not become proportionally easier. In many cases, we've shifted the bottleneck from writing software to reasoning about it.
What's the real problem?
To figure out why this keeps happening, we have to look past the hype of "lines of code per minute" and examine how software actually gets built. When you step back and look at where time goes across a project lifecycle, the core issue becomes obvious:
The fundamental mistake is assuming that typing syntax was the primary bottleneck in software engineering. It wasn't. The real bottlenecks have always been system design, debugging, coordination, testing, and maintainability. This becomes particularly obvious in large engineering organizations and complex open source projects. A change doesn't live in isolation. It interacts with APIs, dependencies, CI pipelines, security tooling, release processes, downstream consumers, and sometimes several architectures and environments.
When you remove the friction of writing code without fixing your capacity to verify and maintain it, you just move the bottleneck elsewhere. Generating code is not the same thing as delivering software:
- AI makes it effortless to open massive PRs or spit out huge text outputs, but human attention doesn't scale. We end up buried in endless review queues, context-switching constantly to review generated code.
- AI may be great at pattern-matching within a single file or function, but it struggles to preserve boundaries, long-term architecture, and system-wide consistency.
- Generating 300 lines of code only takes 5 seconds, but verifying that those lines don't break edge cases or security rules still requires the same amount of human reasoning.
- Metrics like lines of code written or PRs merged go way up, even while actual system reliability and feature quality stay flat or decline.
Lessons learned from experience
Here are some lessons from the field.
The prompting overhead threshold
One of the most frustrating things about working with AI every day is realizing that sometimes the AI isn't actually saving you time. You ask for a quick fix, and the AI dumps a massive response or refactors half the file.
You end up spending so much time steering the prompt, providing context, and correcting small mistakes that you hit a clear realization: if I had just spent that effort writing it myself, it would have been done faster.
When you reach that point, take the keyboard back! Treat AI as an assistant, not a replacement for taking the wheel. When prompt steering costs more energy than writing the logic yourself, stop prompting and write the code.
Context loss (the "Memento" problem)
One of the strangest experiences with coding agents is that they can appear to understand a repository extremely well and then, a few iterations later, behave as if part of the conversation never happened. This feels a bit like the "Memento" movie.
When modifying a codebase, an agent may need to rediscover files, conventions, relationships, and decisions repeatedly. If those constraints aren't encoded somewhere durable, you're relying on the model to reconstruct them from the current context every time. That's not a great foundation for maintaining architecture. This is why repository-level instructions matter. Also, having a large context window doesn't necessarily mean that every important constraint will consistently remain active in the reasoning process.
The default shell headache
A subtle issue on macOS is shell configuration. AI coding agents are very good at generating commands. They're less magical when ensuring those commands meet the actual environment they're running in.
Because macOS defaults to zsh, running commands—especially array manipulations or nested SSH commands—can silently fail or confuse the model.
A practical fix is to explicitly set up a Bash environment for AI tooling, keeping your primary terminal shell (zsh) separate from the terminal profile you assign to your AI automation tools.
$ brew install bash
$ echo "$(brew --prefix)/bin/bash" | \
sudo tee -a /etc/shellsThe lesson is that your development environment is part of the context the agent needs to understand. Unnecessary context needs to be minimized, so that agents can focus on what matters, not on incidental complexity.
Practices that actually help
Instead of letting AI output wild, unconstrained code, high-performing teams set up hard boundaries.
Write explicit repo rules (AGENTS.md or .cursorrules)
Without repository context, AI predicts code based on generic internet patterns. Putting a simple AGENTS.md or .cursorrules file in your root repository dramatically improves output consistency.
# API Rules
- Services cannot directly access database tables outside their domain.
- Use the repository layer only.
- All external HTTP calls require explicit timeouts and retry handling.
- Use structured logging (no raw console output).
- Do not introduce new dependencies without explicit approval.Use "vibe prototyping" not "vibe production"
"Vibe coding"—steering AI purely through prompts without looking closely at the code—is a way to learn or test ideas. But it doesn't belong in production pipelines.
A smart compromise is "vibe prototyping":
- Use AI to build a fast, high-fidelity, interactive prototype (up to ~2,000 lines).
- Use that prototype to validate ideas, UI/UX, and requirements with stakeholders.
- Throw the prototype code away. Keep what you learned from it—the validated requirements, UX, and technical assumptions—and let engineers build the real feature properly within the production architecture.
Related point: Let the agent know what it's doing and don't mix up the modes.
It's either implementing new features, or refactoring, or writing tests, but not everything at the same time, in the same chunk of work. When implementing features or fixing bugs, or (especially) fixing issues in release branches, tell the agent to keep changes to a minimum, to keep the diff reviewable. When refactoring is needed, tell it to perform just the refactoring, in mechanical verifiable steps, possibly by calling out to an LSP or IDE to do the steps (like renames) deterministically. When writing code, instruct it to leave existing documentation comments alone, and don't make gratuitous "by the way" changes.
Stop using one model for everything
Routing tasks to the right model saves both money and time:
- Use heavy reasoning models for architecture design, complex refactoring, distributed systems logic, and deep debugging.
- Use fast, cheap models for boilerplate code, simple unit test scaffolding, documentation, and routine code transformations.
The goal isn't to find the "best AI model", but to find the cheapest model that can reliably solve the problem you're giving it.
AI is fast food for the brain
There's another problem that's easy to ignore because it doesn't show up immediately in a PR: Skill atrophy.
AI-generated code is convenient, but if the AI always does the difficult part, then eventually you stop exercising the part of your brain that understands why the difficult part works. Software engineering intuition comes from getting stuck. From debugging something. From reading the implementation instead of the documentation. From making a terrible abstraction and realizing 6 months later why it was terrible. From understanding how the system behaves underneath the framework.
If an engineer—especially someone early in their career—always asks AI for a solution before trying to understand the problem, they may produce working code but never develop the mental models needed to build reliable systems. That's the part that should worry us more than AI generating bad code.
Bad code can be reviewed. A missing mental model is much harder to detect.
Here are a few habits to develop:
- Spend some time understanding a problem manually before asking AI to solve it.
- Use AI as a sounding board to explain concepts and challenge design decisions, not just as a syntax generator.
- During code review, make sure you can explain the generated code. If you can't defend the logic, then it shouldn't be merged.
- Treat generated code as code written by someone else until you actually understand it.
- Don't only talk to AI about your work. Talk to your fellow team members, too!
Conclusion
AI doesn't replace engineering discipline. It amplifies it.
With clear architecture, strong testing, good observability, defined ownership, and effective code review, AI can significantly accelerate software development. But it doesn't compensate for gaps in those practices. If the architecture is unclear or changes aren't properly tested and reviewed, generating code faster can simply increase the amount of work needed later.
The important distinction is that software delivery is much more than writing code. It includes designing, reviewing, testing, integrating, releasing, operating, debugging, and maintaining software—and those activities still require engineering judgment.
I'm not interested in going back to writing everything by hand. I want AI to reduce the mechanical work so I can spend more time on the engineering decisions that matter.
The goal isn't to type faster. It's to deliver better software, faster.
Resource
Get started with AI for enterprise organizations: A beginner’s guide
About the author
More like this
The last mile problem in agentic AI: Why tool calling reliability is harder than it looks
What risk-aware model deployment looks like in regulated industries
How Red Hat cleared IT debt for scalable AI
Standardizing the AI stack with PyTorch
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Virtualization
The future of enterprise virtualization for your workloads on-premise or across clouds