&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lakshmi Narasimhan</title><link>https://lakshminp.com/</link><description>I help developers build, deploy, and distribute their SaaS without hiring a team. Long-running notes on systems, AI internals, Carnatic music, fiction craft, and whatever else collides interestingly.</description><generator>Hugo + lakshminp theme</generator><language>en-us</language><lastBuildDate>Fri, 24 Apr 2026 00:00:00 +0000</lastBuildDate><managingEditor>Lakshmi Narasimhan</managingEditor><webMaster>Lakshmi Narasimhan</webMaster><copyright>© 2026 Lakshmi Narasimhan</copyright><atom:link href="https://lakshminp.com/feed.xml" rel="self" type="application/rss+xml"/><item><title>Your Cloud Bill Is A Tax On Someone Else's Resume</title><link>https://lakshminp.com/2026/04/cloud-bill-resume-tax/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/cloud-bill-resume-tax/</guid><category>essays</category><category>kubernetes</category><description>There’s an insurance company somewhere — real, working, profitable — with 100,000 monthly users and a peak concurrent load of about 5,000.
They spend high six figures a month on Kubernetes.
They employ twenty people to keep it running.
This story surfaced this week in the Hacker News thread on David Crawshaw’s cloud essay, and the comments section turned into a confessional. Engineer after engineer describing the same pattern: cluster adopted, cluster “optimized,” cloud spend doubled, incidents doubled, and somehow the only thing anyone can agree on is that they need to hire a platform engineer.</description><content:encoded>&lt;![CDATA[<p>There’s an insurance company somewhere — real, working, profitable — with 100,000 monthly users and a peak concurrent load of about 5,000.</p><p>They spend high six figures a month on Kubernetes.</p><p>They employ twenty people to keep it running.</p><p>This story surfaced this week in the Hacker News thread on David Crawshaw’s cloud essay, and the comments section turned into a confessional. Engineer after engineer describing the same pattern: cluster adopted, cluster “optimized,” cloud spend doubled, incidents doubled, and somehow the only thing anyone can agree on is that they need to hire a platform engineer.</p><p>You don’t. You never did. Your entire application would run on a laptop.</p><h2 id="the-incentive-nobody-likes-to-say-out-loud"><strong>The incentive nobody likes to say out loud</strong></h2><p>Here’s the quiet part: your DevOps team does not choose infrastructure based on what your application needs.</p><p>They choose it based on what their next job will pay for.</p><p>Kubernetes on a resume is worth more than Docker Compose on a resume. Terraform on a resume is worth more than “I SSH’d into the box.” Managed EKS on a resume is worth more than “I run a VM.” Every procurement decision in a modern engineering org is being made by someone who, at some level, is also writing the next page of their LinkedIn.</p><p>And management, god bless them, trusts the sales and marketing departments of Datadog and AWS and HashiCorp more than they trust their own engineers. So when someone internally says “we could do this on one server,” and someone externally sends a deck titled<em>Scaling Your Platform For The Future</em>, guess which one wins the meeting.</p><p>The decision was never technical. You just paid the technical price for it.</p><h2 id="kubernetes-is-not-the-villain-the-scale-is"><strong>Kubernetes is not the villain. The scale is.</strong></h2><p>Let’s be precise, because “Kubernetes” is doing a lot of work in this essay.</p><p>Full enterprise Kubernetes — managed control planes, service meshes, operators for everything, a dedicated platform team, Helm charts nested inside Helm charts like Russian dolls of YAML — that thing was built for Google’s problem. Multi-tenant, multi-region, thousands of services, teams that don’t talk to each other.</p><p>If your org does not look like that, you are wearing a costume.</p><p>K3s on a single VPS is not the same animal. Docker Compose on a single VPS is not the same animal. Kamal shipping containers to one Debian box is not the same animal. Those are orchestration for people who want one sane way to deploy a container, not a career in platform engineering.</p><p>The HN thread is full of<a href="https://blog.lakshminp.com/p/kubernetes-indie-dev-alternative" rel="external nofollow noopener" class="lnp-link">engineers who moved from full K8s to one of these simpler setups<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. The reports are boringly consistent: costs collapsed, incidents dropped, debugging became possible again. Nobody was shocked. Everyone had been waiting for permission to say it.</p><h2 id="the-solo-founders-version-of-this-trap"><strong>The solo founder’s version of this trap</strong></h2><p>You are not the insurance company. You do not have twenty people. You have you, and maybe a contractor, and a credit card that is getting nervous.</p><p>And yet — you will read the AWS Well-Architected Framework. You will follow a tutorial that starts with “first, let’s set up your VPC.” You will pay $80/month for a managed database to store 200 rows. You will provision a load balancer in front of one server. You will copy the shape of infrastructure you saw at your day job, because that shape felt legitimate, and you want to feel legitimate too.</p><p>This is how solo founders end up with a<a href="https://blog.lakshminp.com/p/aws-is-overrated" rel="external nofollow noopener" class="lnp-link">$600/month AWS bill for an app that has six users<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</p><p>The shape of legitimacy is the trap. Nobody cares what your infrastructure looks like until you have customers, and once you have customers,<a href="https://blog.lakshminp.com/p/30-dollar-saas-stack" rel="external nofollow noopener" class="lnp-link">“my app runs on one $12 VPS”<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> is a story people<em>love</em>. It’s the opposite of suspicious. It’s proof that the thing works.</p><h2 id="what-to-actually-do"><strong>What to actually do</strong></h2><ol><li><p><strong>One machine until you can’t.</strong> One VPS. One Postgres on that VPS. One reverse proxy. Docker Compose or Kamal to deploy. You are allowed to stop here for years.</p></li><li><p><strong>Scale vertically first.</strong> Hetzner will rent you a 48-core EPYC machine with 256 GB of RAM for €199/month. A mid-tier managed Kubernetes cluster on AWS starts at more than that before you’ve run a single pod. Most apps die from bad unit economics, not from running out of CPU.</p></li><li><p><strong>When you outgrow that — and you might not —<a href="https://blog.lakshminp.com/p/when-diy-beats-managed-kubernetes" rel="external nofollow noopener" class="lnp-link">K3s on a few boxes gives you orchestration without the org chart<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</strong> This is the actual sweet spot for a solo operator who needs more than one machine but less than a platform team.</p></li><li><p><strong>Treat every infrastructure recommendation as a resume artifact until proven otherwise.</strong> Ask who benefits if you adopt this. If the answer is “the person telling me to adopt it,” weigh accordingly.</p></li><li><p><strong>Your cloud bill is a leading indicator of how much time you are spending on things that do not make your product better.</strong> Watch it like you watch your weight.</p></li></ol><p>The cloud was supposed to be leverage. For most people, most of the time, it has become the opposite: a recurring invoice for someone else’s credibility.</p><p>You are allowed to just run the server.</p>
]]></content:encoded></item><item><title>The Real SaaS Moat AI Can't Replicate</title><link>https://lakshminp.com/2026/02/ai-saas-moat-exposed/</link><pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/ai-saas-moat-exposed/</guid><category>essays</category><category>saas</category><description>There’s a comment buried 14 levels deep in this Hacker News thread about AI killing B2B SaaS ↗. It has 37 upvotes and it’s the smartest thing I’ve read this year.
Here it is, paraphrased: “The real innovation of SaaS was laundering inaccessible open-source software into a format that doesn’t require transiting git. The hard part was never the code. The hard part was that git sucks.”
I laughed. Then I stopped laughing because it’s devastatingly correct.</description><content:encoded>&lt;![CDATA[<p>There’s a comment buried 14 levels deep in<a href="https://news.ycombinator.com/item?id=46888441" rel="external nofollow noopener" class="lnp-link">this Hacker News thread about AI killing B2B SaaS<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. It has 37 upvotes and it’s the smartest thing I’ve read this year.</p><p>Here it is, paraphrased: “The real innovation of SaaS was laundering inaccessible open-source software into a format that doesn’t require transiting git. The hard part was never the code. The hard part was that git sucks.”</p><p>I laughed. Then I stopped laughing because it’s devastatingly correct.</p><h2 id="the-git-laundering-machine"><strong>The Git Laundering Machine</strong></h2><p>Think about the most profitable SaaS businesses in technology. Seriously, list them.</p><p>AWS? That’s Linux, KVM, and Xen behind a billing dashboard. Heroku was git-push-to-deploy because deploying was too hard. Vercel is the same thing for Next.js. MongoDB Atlas is MongoDB without the ops. Redis Cloud is Redis without the YAML. Supabase is Postgres without the DBA.</p><p>Every single one of them is a factory that converts something freely available on GitHub into something you can pay for on a website.</p><p>The commenter was right. These companies didn’t build moats with proprietary technology. They built moats by standing between users and git. Their value proposition, stripped to the studs, is: “You don’t have to clone a repo.”</p><p>That’s a $500 billion industry built on the fact that<code>git clone</code> is scary.</p><h2 id="llms-just-killed-the-middleman"><strong>LLMs Just Killed the Middleman</strong></h2><p>Here’s where the “AI is killing SaaS” thesis gets real.</p><p>When a CTO says “can we build this internally?”, the old answer was: “Technically yes, but you’d need 3 engineers, 6 months, and ongoing maintenance. Just buy the SaaS.”</p><p>The new answer: “ChatGPT set it up in 20 minutes. It reads from the same open-source code the SaaS vendor uses. It runs on our infrastructure. There’s no monthly bill.”</p><p>LLMs do exactly what SaaS companies do — they take inaccessible open-source software and make it usable by normal humans. They just skip the subscription.</p><p>The git laundering machine now has competition. And the competitor works for free.</p><h2 id="what-actually-survives"><strong>What Actually Survives</strong></h2><p>So is B2B SaaS dead? No. But the moat map just got redrawn.</p><p>Here’s what doesn’t survive:<strong>any SaaS whose primary value is “we set it up so you don’t have to.”</strong> Deployment wrappers, config GUIs, managed hosting for commodity databases — all of this is getting compressed.</p><p>An HN commenter who manages teams put it bluntly: “Management doesn’t want to be responsible for bespoke internal tools.” That’s real. But it’s a shrinking moat. Today’s management doesn’t want to be responsible. Tomorrow’s management grew up with ChatGPT and doesn’t see internal tooling as risky.</p><p>Here’s what survives:</p><p><strong>Data.</strong> If your SaaS accumulates proprietary data over time — customer behavior patterns, industry benchmarks, network effects — that’s a moat AI can’t replicate. A new LLM-generated tool starts with zero data. Your SaaS has three years of it.</p><p><strong>Compliance and trust.</strong> SOC 2, HIPAA, GDPR certification takes time and money. “ChatGPT built it” doesn’t pass an enterprise security audit. Yet.</p><p><strong>Workflow lock-in.</strong> Not the software itself, but the habits. Slack isn’t hard to replace technically. It’s hard to replace because your whole company’s muscle memory lives there.</p><p><strong>Network effects.</strong> Figma isn’t valuable because of the rendering engine. It’s valuable because your designers, developers, and product managers are all in the same file. That’s a moat no amount of vibe coding can replicate.</p><p><strong>The specification itself.</strong> Here’s the contrarian take within the contrarian take: as code becomes commodity, the spec becomes the product. The companies that survive aren’t the ones that write the best code. They’re the ones that understand the problem deeply enough to specify what “right” looks like. Everyone else is just a GPT wrapper with a landing page.</p><h2 id="the-indie-saas-playbook-changes"><strong>The Indie SaaS Playbook Changes</strong></h2><p>If you’re building SaaS solo — and if you’re reading this newsletter, you probably are — the implications are brutal and clear.</p><p><em>Full disclosure: I built a product that does exactly this.<a href="https://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">Supabyoi<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> deploys Supabase for you. By my own thesis, that’s a shrinking moat. I’m writing this post partly because I’m living the question: evolve or get compressed.</em></p><p><strong>Stop building tools. Start building data flywheels.</strong></p><p>A CRUD app with a nice UI is now a weekend project for anyone with ChatGPT. A system that gets smarter with every user interaction is still a real business.</p><p><strong>Stop selling setup. Start selling ongoing value.</strong></p><p>“We deploy Postgres for you” is dying. “We analyze your Postgres performance patterns across 10,000 databases and tell you what’s about to break” is thriving.</p><p><strong>Stop competing on features. Start competing on understanding.</strong></p><p>The SaaS products that survive AI commodification will be the ones that understand their customers’ problems better than a general-purpose LLM ever could. Domain expertise is the last moat.</p><h2 id="the-500-billion-question"><strong>The $500 Billion Question</strong></h2><p>The HN thread devolved into the usual “AI is overhyped” vs. “AI changes everything” tribal warfare. But that one comment, buried 14 levels deep, cut through all of it.</p><p>The SaaS moat was never the software. It was the fact that software was hard to access. That moat is evaporating.</p><p>What’s left is data, trust, network effects, and deep domain understanding.</p><p>Build your SaaS around those. Or enjoy competing with a free chatbot.</p>
]]></content:encoded></item><item><title>Software Engineering Is Dead, or Is It?</title><link>https://lakshminp.com/2026/01/software-engineering-dead/</link><pubDate>Tue, 27 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/software-engineering-dead/</guid><category>essays</category><category>craft</category><description>Everyone said agentic coding would kill software engineering discipline. Turns out it killed the wrong disciplines.
Clean code? Dead ↗. Nobody’s hand-crafting variable names when Claude generates 500 lines in 30 seconds. But TDD, specs-driven development, domain-driven design — the stuff we used to skip because it felt like ceremony? That’s the load-bearing wall now. Tear it out and the whole thing collapses.
TDD: The Cache That Wasn’t I had Claude Code build me a Redis caching module. Proper TTLs. Cache invalidation on writes. Unit tests passing. Beautiful, elegant, chef’s-kiss code.</description><content:encoded>&lt;![CDATA[<p>Everyone said agentic coding would kill software engineering discipline. Turns out it killed the<em>wrong</em> disciplines.</p><p>Clean code?<a href="https://lakshminp.substack.com/p/clean-code-is-dead-long-live-clean" rel="external nofollow noopener" class="lnp-link">Dead<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. Nobody’s hand-crafting variable names when Claude generates 500 lines in 30 seconds. But TDD, specs-driven development, domain-driven design — the stuff we used to skip because it felt like ceremony? That’s the load-bearing wall now. Tear it out and the whole thing collapses.</p><h2 id="tdd-the-cache-that-wasnt"><strong>TDD: The Cache That Wasn’t</strong></h2><p>I had Claude Code build me a Redis caching module. Proper TTLs. Cache invalidation on writes. Unit tests passing. Beautiful, elegant, chef’s-kiss code.</p><p>One problem. The actual query functions never called the caching layer.</p><p>Hundreds of requests later, I checked Redis. Empty. A pristine, untouched Redis instance, sitting there like a museum exhibit.<a href="https://lakshminp.substack.com/p/claude-code-is-incredible-it-also" rel="external nofollow noopener" class="lnp-link">I’ve written about these failure patterns before<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> — this one hurt the most.</p><p>Integration tests would have caught it. But only if I’d written them<em>first</em>. That’s the part everyone skips — writing the verification before the implementation. TDD forces you to define “done” before the agent starts building. Without it, you get beautiful isolated components that nobody wired together.</p><p>This isn’t hypothetical. An r/programming thread (894 upvotes) nailed it: “We’re getting correct code, but not right code.” One reviewer found AI-generated Java using the default ForkJoinPool for I/O-bound tasks. Compiles fine. Passes unit tests. Catastrophic under load.</p><p>My favorite was the “chief architect” who generated “full coverage” unit tests with Copilot. Duplicate asserts. Unused service constructions. Tests that passed but tested nothing. A green CI pipeline that was essentially a participation trophy.</p><p>TDD isn’t ceremony anymore. It’s the spec your agent actually follows.</p><h2 id="specs-driven-development-the-authentication-amnesia"><strong>Specs-Driven Development: The Authentication Amnesia</strong></h2><p>I spent two weeks pair-programming authentication with Claude Code. We tracked race conditions together. Debated RS256 vs HS256. Built a shared understanding of every edge case.</p><p>Then compaction hit.</p><p>“Where did we leave off?”</p><p>“I don’t have information about previous sessions.”</p><p>Two weeks of context. Gone. My<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> became a graveyard of cryptic notes that made sense to exactly nobody, including me three days later.<a href="https://lakshminp.substack.com/p/why-your-ai-wakes-up-every-morning" rel="external nofollow noopener" class="lnp-link">I wrote the full horror story here<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</p><p>So I started using a git-backed issue tracker with dependency graphs that persists across agent sessions. Sprints and epics stopped being PM ceremony and became the agent’s memory. The control plane for multi-session work.</p><p>The pattern scales beyond my personal disasters. An r/programming post titled “The era of AI slop cleanup has begun” (4,200 upvotes) described a freelancer who keeps getting hired to fix AI-generated codebases. “It mostly works, but does so terribly.” The missing ingredient every single time: no structured planning, no phased delivery. Just vibes and a prompt.</p><p>Fred Brooks said it decades ago, and r/ExperiencedDevs rediscovered it (1,400 upvotes): “Once requirements are fully expressed, their information content is fixed. You can change surface syntax, but you can’t compress semantics.”</p><p>You can’t skip the thinking. You can only skip writing it down — and then you pay for it later when your agent wakes up with amnesia.</p><h2 id="ddd-the-firewall-agents-cant-generate"><strong>DDD: The Firewall Agents Can’t Generate</strong></h2><p>Here’s a<a href="https://reddit.com/r/programming/comments/1nxobte/the_phantom_author_in_our_codebases_why/" rel="external nofollow noopener" class="lnp-link">Reddit thread<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> that lives in my head rent-free. Someone described the “Phantom Author” problem — only domain experts catch the subtle flaws agents produce. The code compiles. The tests pass. The logic is plausible. But it’s<em>wrong</em> in ways only someone who understands the domain would notice.</p><p>The punchline: “Ironically the only people who should be using AI are people who are already experts.”</p><p>Bounded contexts — the core DDD concept — are the firewall. They tell the agent where one domain ends and another begins. Without that modeling, agents connect everything to everything. Your billing module knows about your notification preferences. Your auth layer has opinions about your recommendation engine.</p><p>Agents can’t generate domain boundaries because domain boundaries come from understanding the business, not the code. That’s your job. The agent’s job is everything inside the boundary.</p><h2 id="the-punchline"><strong>The Punchline</strong></h2><p>The disciplines that survived aren’t the ones that made code pretty. They’re the ones that tame complexity.</p><p>TDD tells the agent what “done” means. Specs give it memory across sessions. DDD gives it boundaries it can’t infer on its own.</p><p>We didn’t need less engineering discipline. We needed<em>different</em> engineering discipline. The ceremony is dead. The structure is mandatory.</p>
]]></content:encoded></item><item><title>The AI Productivity Paradox: Why I'm Working More Than Ever</title><link>https://lakshminp.com/2026/01/ai-productivity-paradox/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/ai-productivity-paradox/</guid><category>essays</category><category>craft</category><description>I had a conversation with a friend last week that I can’t stop thinking about.
We were comparing notes on hitting usage limits with AI coding tools. Both of us on expensive plans. Both of us running into ceilings more often than we did months ago. Both of us, apparently, turning into “power users” in our respective tiers.
And then he dropped this line: “So AI was supposed to make us work less but now we are working more. That’s the conclusion.”</description><content:encoded>&lt;![CDATA[<p>I had a conversation with a friend last week that I can’t stop thinking about.</p><p>We were comparing notes on hitting usage limits with AI coding tools. Both of us on expensive plans. Both of us running into ceilings more often than we did months ago. Both of us, apparently, turning into “power users” in our respective tiers.</p><p>And then he dropped this line: “So AI was supposed to make us work less but now we are working more. That’s the conclusion.”</p><p>I laughed. Then I stopped laughing.</p><p>Because he’s right. I get more done in a single day than I used to accomplish in a week. I’m shipping features, writing content, running experiments at a pace that would’ve been unthinkable about a year ago.</p><p>And I have never worked this much in my life.</p><p>Here’s what nobody warned us about: AI didn’t give us more time. It gave us more capability.</p><p>And capability, it turns out, is extremely addictive.</p><h2 id="the-collapse-of-activation-energy"><strong>The Collapse of Activation Energy</strong></h2><p>Before AI coding assistants, most ideas died a quiet death in my notes app. Not because they were bad ideas. Because the effort-to-value ratio was unfavorable.</p><p>“I could build that feature, but it would take a week of focused work. Is it worth a week? Probably not.”</p><p>Idea archived. Moving on.</p><p>Now that same feature takes a day. Sometimes less. So I build it.</p><p>Then I build the next thing. And the next. And suddenly I’m shipping more in a month than I used to ship in a quarter.</p><p>The activation energy for starting new work collapsed. And I filled every inch of the newly available space.</p><h2 id="ambition-scales-with-output"><strong>Ambition Scales With Output</strong></h2><p>Here’s the thing about humans: we don’t scope our ambitions in absolute terms. We scope them relative to what feels achievable.</p><p>Before AI, I planned projects based on what I could reasonably ship with my limited time and energy. A feature per week. Maybe two if I was focused.</p><p>Now “reasonable” means something entirely different. My mental model of what’s achievable expanded by 5x, and my project scope expanded right along with it.</p><p>I’m not doing the same work faster. I’m doing<em>more work</em>.</p><p>The goalposts moved. And I moved them myself.</p><h2 id="the-death-of-natural-stopping-points"><strong>The Death of Natural Stopping Points</strong></h2><p>There used to be friction in development work. Waiting for builds. Context switching costs. The mental load of holding an entire system in your head while debugging.</p><p>That friction was annoying. It was also a circuit breaker.</p><p>It forced breaks. It created natural pauses where you’d step away, get coffee, maybe realize it was 7pm and you should probably eat dinner.</p><p>AI removed the friction. Which sounds great until you realize the friction was also your automatic brake pedal.</p><p>Now you can go from idea to implementation to deployment without ever hitting a natural stopping point. The only thing that stops you is your own willpower.</p><p>My willpower, for the record, is not great.</p><h2 id="the-dopamine-loop-of-shipping"><strong>The Dopamine Loop of Shipping</strong></h2><p>Here’s an uncomfortable comparison: AI-assisted coding feels a lot like infinite scroll.</p><p>You ship something. It feels good. The tool makes shipping fast and easy. So you ship something else. That also feels good. And there’s always one more thing you could ship.</p><p>Same psychological mechanics. Different output.</p><p>Except instead of consuming content, you’re producing it. Which feels more virtuous. Which makes it even harder to stop.</p><p>“I’m not doomscrolling. I’m being<em>productive</em>.”</p><p>Sure you are.</p><h2 id="the-why-not-threshold"><strong>The “Why Not” Threshold</strong></h2><p>The most insidious change is what happened to my internal cost-benefit calculator.</p><p>I used to ask: “Is this worth the effort?”</p><p>Now I ask: “Why wouldn’t I just do this?”</p><p>That experiment I would’ve skipped because setting it up was tedious? Now I run it. That edge case I would’ve ignored because fixing it properly would take half a day? Now I fix it.</p><p>The threshold for “worth my time” dropped to near zero. So everything is worth my time. So I do everything.</p><p>This is how you end up working 12-hour days while technically being more “efficient” than ever before.</p><h2 id="the-uncomfortable-truth"><strong>The Uncomfortable Truth</strong></h2><p>AI tools didn’t give us more free time. They gave us more output capacity. And we’re psychologically incapable of leaving capacity unused. At least I am.</p><p>The work expanded to fill the available capability. Parkinson’s Law, but in reverse.</p><p>We’re not working less. We’re shipping more while<em>feeling</em> productive. Which is a different thing entirely.</p><p>My friend was right to put “off” in scare quotes when wishing me a good weekend. We both knew I wasn’t really taking time off. I was just switching to a different kind of work.</p><h2 id="what-now"><strong>What Now?</strong></h2><p>I don’t have a tidy solution here. I’m not going to pretend I’ve figured out work-life balance in the age of AI assistants.</p><p>But I’ve started noticing when I’m filling capacity just because I can. When I’m starting a new feature not because it matters, but because the activation energy is so low that “why not” won the argument.</p><p>Sometimes the answer to “why not” is: because you could just&hellip; not.</p><p>Groundbreaking insight, I realize.</p><p>The AI isn’t going to set boundaries for you. If anything, hitting usage limits might be the only forced break some of us get. Which is both sad and a little funny.</p><p>Maybe the real productivity hack is learning to leave capability on the table.</p><p>I’ll let you know how that goes. Right after I ship this one more thing.</p><hr><p><em>I write about building and deploying software as a solo developer. If you’re trying to do it all yourself without hiring a team, I’m probably making the same mistakes you are.</em></p>
]]></content:encoded></item><item><title>I Built 2 SaaS Products Vibe Coding. Here's the System That Made It Work.</title><link>https://lakshminp.com/2026/01/vibe-coding-2-saas-products/</link><pubDate>Sat, 24 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/vibe-coding-2-saas-products/</guid><category>essays</category><category>saas</category><category>ai-coding</category><description>Gene Kim and Steve Yegge’s Vibe Coding ↗ book says you’re the head chef now.
The metaphor runs through the whole thing: you’re not a line cook anymore, you’re orchestrating AI sous chefs, directing the kitchen, tasting every dish before it goes out. The developer-as-implementer era is over. Welcome to developer-as-orchestrator.
The Biryani Incident It’s a good metaphor. I buy it. But here’s the thing about being a head chef that the metaphor doesn’t quite capture: a head chef without mise en place is just a guy having a panic attack near hot surfaces.</description><content:encoded>&lt;![CDATA[<p>Gene Kim and Steve Yegge’s<a href="https://www.amazon.com/Vibe-Coding-Building-Production-Grade-Software/dp/1966280025" rel="external nofollow noopener" class="lnp-link">Vibe Coding<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> book says you’re the head chef now.</p><p>The metaphor runs through the whole thing: you’re not a line cook anymore, you’re orchestrating AI sous chefs, directing the kitchen, tasting every dish before it goes out. The developer-as-implementer era is over. Welcome to developer-as-orchestrator.</p><h2 id="the-biryani-incident"><strong>The Biryani Incident</strong></h2><p>It’s a good metaphor. I buy it. But here’s the thing about being a head chef that the metaphor doesn’t quite capture: a head chef without mise en place is just a guy having a panic attack near hot surfaces.</p><p>I know this because I’ve been that guy. Literally.</p><p>My wife had to leave town for a few days. “I’ll handle dinner,” I said, with the confidence of someone who has watched many cooking videos and successfully boiled pasta multiple times. I decided to make veg biryani — a dish my wife makes effortlessly, layering rice and vegetables and spices into something that tastes like it required more effort than it actually did.</p><p>“Prep everything first,” she told me before leaving. “Soak the basmati rice. Marinate the paneer. Chop the vegetables for layering. Have it all ready before you start cooking.”</p><p>Reader, I did not do this.</p><p>I started frying onions. While the onions were going, I realized I hadn’t marinated the paneer. So I started cubing paneer and mixing yogurt and spices. Then the onions started burning. I ran back, stirred frantically, ran back to the paneer. Remembered I needed to soak the basmati. Started the rice soaking. The onions were now definitely burned. I scraped them out, started over, but now I was behind, so I tried to do the vegetables and the new onions simultaneously while the paneer sat half-marinated&hellip;</p><p>An hour later I had a kitchen that looked like a crime scene, three pans with various stages of failure in them, and something that was technically edible but bore no resemblance to biryani. My wife, via video call, watched me plate this disaster with the expression of someone who had specifically warned against this exact outcome.</p><p>The problem wasn’t skill. I can cook. The problem was that prep and execution were bleeding into each other. I was trying to figure out what I needed while also doing the thing. And it turns out you can’t actually do both. Not well, anyway.</p><p>I’ve been that guy with AI sous chefs too.</p><p>I’ve been vibe coding since mid-2025. By “vibe coding” I mean the thing where you describe what you want in natural language and an AI writes the code. You know, the future we were promised, except the future has some sharp edges nobody mentioned in the demos.</p><p>Two SaaS products. Real users. Real revenue. Not toy projects, not “look ma I generated a todo app” tutorials, not the kind of thing you show off on Twitter and then quietly delete three weeks later. Actual products that people pay actual money for.</p><p>So when I tell you what follows, understand: this isn’t theory. This is what I learned by shipping real things and watching everything that could go wrong go wrong.</p><h2 id="the-markdown-hemorrhage"><strong>The Markdown Hemorrhage</strong></h2><p>For the first few months, I was that chef.</p><p>I’d sit down to implement a feature. Claude and I would get rolling. Then I’d notice a bug. Well, I’m already here, might as well fix the bug. Then while fixing the bug, I’d realize the error handling was inconsistent. Better clean that up. Oh, and there’s still context left in the window — might as well tackle that other feature I’ve been meaning to add.</p><p>Two hours later: three half-finished things, Claude confused about which task we’re actually doing, and code quality somewhere between “works” and “I’m not sure why.”</p><p>And the markdown. God, the markdown.</p><p>Claude, bless its heart, wanted to help me remember things. So it started creating files.<a href="http://architecture.md/" rel="external nofollow noopener" class="lnp-link">ARCHITECTURE.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.<a href="http://decisions.md/" rel="external nofollow noopener" class="lnp-link">DECISIONS.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. IMPLEMENTATION_NOTES.md.<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.<a href="http://context.md/" rel="external nofollow noopener" class="lnp-link">CONTEXT.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.<a href="http://changelog.md/" rel="external nofollow noopener" class="lnp-link">CHANGELOG.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. README_UPDATED.md.</p><p>I call this markdown hemorrhage. The AI equivalent of a kitchen where every surface is covered with prep bowls, half-chopped vegetables, and sticky notes that say “DON’T FORGET THE SAUCE” — technically documentation, practically chaos.</p><p>At one point I had so many markdown files that I needed another AI tool just to search through the documentation I’d created for my AI tool.</p><p>This was clearly insane.</p><p>But here’s the thing that took me embarrassingly long to figure out: the problem wasn’t the tools. The problem was me.</p><h2 id="one-goal-per-session"><strong>One Goal Per Session</strong></h2><p>I was treating every Claude session like a buffet.</p><p>You know how it goes. You sit down to implement a feature. While you’re implementing, you notice a bug. Well, you’re already here, might as well fix the bug. Oh, and while fixing the bug, you realize the error handling is inconsistent across the codebase. Better clean that up too. And hey, there’s still context left in the window — might as well tackle that other feature you’ve been meaning to add.</p><p>Two hours later, you’ve got three half-finished things, Claude is confused about which task it’s actually working on, and the code quality has degraded to “works but I’m not sure why.”</p><p>I call this context pollution. And once I named it, I started seeing it everywhere.</p><p>LLMs are bad at juggling multiple goals. This isn’t a Claude problem — it’s a fundamental thing about how these models work. When you ask them to hold multiple objectives simultaneously, they get worse at all of them. Not a little worse.<em>Dramatically</em> worse.</p><p>The fix sounds almost stupidly simple: one goal per session.</p><p>That’s it. That’s the whole trick. One goal. One session. If you discover a bug while implementing a feature, you write down the bug and you close the session. The bug gets its own session later. No “while I’m here” detours. No context pollution.</p><p>“But what about efficiency?” I hear you asking. “Isn’t it wasteful to end a session when there’s still context left?”</p><p>This is the trap. This is exactly the thinking that leads to burned onions and half-marinated paneer. The leftover context is not an asset. It’s a liability. It’s your coworker with three tasks open, doing all of them poorly, about to forget everything anyway.</p><p>End the session. Start fresh. One goal.</p><h2 id="the-mise-en-place"><strong>The Mise en Place</strong></h2><p>Now, this discipline only works if you have a way to track what you’re not doing.</p><p>If you end a session every time you discover a bug, you need somewhere for that bug to live. Otherwise you’ll forget it. The bugs pile up in your head, you context-switch mentally, and you’re back where you started.</p><p>This is where beads comes in.</p><p>Beads is a git-backed issue tracker that Claude can read and write. Steve Yegge built it (yes, that Steve Yegge — the guy who wrote the platforms rant and approximately nine million words about Emacs). The idea is simple: every task becomes a “bead.” Claude creates them, updates them, closes them. They survive compaction. They sync through git.</p><p>I installed it. I ran<code>bd init</code>. And then something clicked.</p><p>See, beads isn’t just a todo list. It’s a forcing function. When you start a session, you run<code>bd ready</code> and it shows you what’s available to work on. You pick<em>one</em>. Not three. One.</p><p>And when you discover a bug mid-session? You tell Claude to create a bead for it. Claude writes it down, logs the context, notes any relevant details. Then you move on. The bug exists now. It has a home. You don’t have to hold it in your head.</p><p>The discipline and the tool reinforce each other. One bead per session only works because beads exist to capture everything else. And beads only work because the discipline prevents you from drowning in them.</p><h2 id="grooming-vs-coding"><strong>Grooming vs. Coding</strong></h2><p>But I’m getting ahead of myself. Let me tell you about grooming.</p><p>In my old workflow, I’d sit down and just&hellip; start. Open Claude, describe what I wanted, begin coding. Very vibe. Very chaotic. Whatever felt right in the moment.</p><p>The problem is that “figuring out what to do” and “doing the thing” are completely different cognitive modes. One is divergent — you’re exploring possibilities, breaking down problems, identifying edge cases. The other is convergent — you’re executing, making decisions, writing code.</p><p>When you mix them, you get mush.</p><p>So now I run two types of sessions:</p><p>Grooming sessions are for thinking. I’m not coding. I’m not even planning to code in this session. I’m creating beads. Breaking down a feature into pieces. Identifying dependencies. Noting edge cases. If I think of an unrelated feature while grooming, it gets written down — for a different grooming session. No cross-contamination.</p><p>Coding sessions are for execution. One bead. Implement it. If I discover a bug, I note it and keep going unless it’s blocking. The bug gets groomed and coded in its own sessions later.</p><p>This separation is the whole game. It sounds bureaucratic. It sounds like exactly the kind of process that “vibe coding” was supposed to eliminate. But here’s the secret: this discipline is what makes vibe coding actually work at scale. Without it, you’re just generating code and hoping. With it, you’re building systems.</p><h2 id="a-few-other-things"><strong>A Few Other Things</strong></h2><p>MCPs should be loaded at project level, not globally. Every MCP eats context. If a project doesn’t need the Reddit MCP, it doesn’t get the Reddit MCP. Context is expensive. Guard it like it’s money, because in a very real sense, it is.</p><p>Autocompact should be off. I want to control when context resets, not have the algorithm decide for me mid-feature. Yes, this means manually managing sessions. That’s the point.</p><p><a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">Claude.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> files are more powerful than you think. I have a global one in<code>~/.claude/CLAUDE.md</code> with rules that apply everywhere. Each project gets its own with project-specific instructions. Claude reads these automatically. They’re like a pre-prompt that doesn’t eat your context window.</p><h2 id="what-still-doesnt-work"><strong>What Still Doesn’t Work</strong></h2><p>Now, here’s the part where I’m supposed to tell you it’s all solved and my workflow is perfect.</p><p>It’s not.</p><p>Debugging production issues is still clunky. I’ve got a combination of skills and MCPs that sort of works, but there’s too much manual context assembly. Something breaks in prod and I’m still spending the first 20 minutes of the session explaining the architecture before we can even start diagnosing.</p><p>Test-driven development doesn’t flow. The loop of “write test, see it fail, implement, see it pass” — it’s awkward. Claude wants to write everything at once. I’m still tweaking my tooling to make TDD feel natural.</p><p>UX work is hard. Like, fundamentally hard. Claude can scaffold UI. It can generate components. But “does this feel right?” is a human judgment call, and trying to get there through text-based iteration is like describing a painting to someone and asking them to tell you if it’s beautiful.</p><p>These are the walls I’m hitting. I’m building tooling to address them — an<a href="https://lakshminp.substack.com/p/why-im-building-an-agent-orchestrator" rel="external nofollow noopener" class="lnp-link">agent orchestrator<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> that tailors Claude to my specific workflow. Work in progress. If you’re the adventurous type, you can<a href="https://badri.github.io/wt/" rel="external nofollow noopener" class="lnp-link">try it now<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</p><h2 id="the-system"><strong>The System</strong></h2><p>So here’s the actual system, if you want to try it:</p><ol><li><p>Install beads:<code>npm install -g @anthropic-ai/beads &amp;&amp; bd init</code></p></li><li><p>Add to your global<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>: “Check<code>bd ready</code> at session start. One bead per session.”</p></li><li><p>Separate grooming from coding. Different sessions. Different mindsets.</p></li><li><p>Resist the urge to “do more while there’s context left.” That’s the trap.</p></li><li><p>Protect your context. Project-level MCPs only. Kill anything you don’t need.</p></li></ol><p>Two SaaS products since mid-2025. All vibe coded with this system.</p><p>Not because the tools are magic. The tools are good, but tools are never magic. What made it work was the discipline — the willingness to be a little bit boring about context hygiene, to resist the temptation to do more, to trust that a focused session ships more than a scattered one.</p><p>Vibe coding without chaos. It turns out it’s not about vibing harder. It’s about vibing deliberately.</p><p>You’re the head chef now. But don’t forget your mise en place.</p><p>My wife was right, by the way. She usually is.</p><hr><p><em>I’m Lakshmi. 20 years in software — ops, infrastructure, full-stack. Now solo founder using Claude Code to develop, deploy, and distribute.</em></p>
]]></content:encoded></item><item><title>Congratulations, You've Been Promoted to Code Janitor</title><link>https://lakshminp.com/2026/01/code-janitor-ai-era/</link><pubDate>Fri, 23 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/code-janitor-ai-era/</guid><category>essays</category><category>craft</category><description>It was 2001. I was building a platformer.
Not “building” in the modern sense, where you describe what you want and a language model hallucinates it into existence. I mean building. DJGPP. Allegro. A DOS compiler that ran on Windows 98 and made you feel like a wizard for getting it to work at all.
I spent three weeks figuring out how platform scrolling worked.
Three weeks. Not because I was stupid — though jury’s still out — but because nobody had written a Medium article explaining it. Stack Overflow didn’t exist. The Allegro documentation was a text file that assumed you already knew what a framebuffer was. I had to think.</description><content:encoded>&lt;![CDATA[<p>It was 2001. I was building a platformer.</p><p>Not “building” in the modern sense, where you describe what you want and a language model hallucinates it into existence. I mean<em>building</em>. DJGPP. Allegro. A DOS compiler that ran on Windows 98 and made you feel like a wizard for getting it to work at all.</p><p>I spent three weeks figuring out how platform scrolling worked.</p><p>Three weeks. Not because I was stupid — though jury’s still out — but because nobody had written a Medium article explaining it. Stack Overflow didn’t exist. The Allegro documentation was a text file that assumed you already knew what a framebuffer was. I had to<em>think</em>.</p><p>And then one night, around 2am, I got it working.</p><p>My little sprite — a 16x16 pixel abomination that was supposed to be a knight but looked more like a confused rectangle — walked across the screen. I pressed the arrow keys and the platform scrolled. The background moved. The character stayed centred.</p><p>I decided, right then, that I wanted to be a game programmer.</p><p>(I didn’t become a game programmer. Life had other plans. But that’s not the point.)</p><p>The point is: I remember that moment with perfect clarity. The dopamine hit. The sense of<em>creation</em>. I had figured something out. I had made something move. I understood, down to the register level, why it worked.</p><p>I couldn’t tell you the last time I felt that.</p><h2 id="the-joy-we-traded"><strong>The Joy We Traded</strong></h2><p>There’s a thread on r/ClaudeAI that’s been haunting me. 624 upvotes. Title: “We are not developers anymore, we are reviewers.”</p><p>The author nails it:</p><blockquote><p><em>“Coding used to be a creative act. You enter a ‘flow state,’ solving micro-problems and building something from nothing. Now, the workflow is: Prompt → Generate → Read Code → Fix Code. We have effectively turned the job into an endless Code Review session.”</em></p></blockquote><p>And then the kicker:</p><blockquote><p><em>“Let’s be honest, code review has always been the most tedious part of the job.”</em></p></blockquote><p>Yeah. That landed.</p><p>I used to joke that the worst part of being a senior engineer was reviewing other people’s code. All the cognitive load of understanding a system, none of the satisfaction of building it. You’re not creating — you’re auditing. You’re the IRS of software development.</p><p>Congratulations. That’s your whole job now.</p><h2 id="the-janitor-effect"><strong>The Janitor Effect</strong></h2><p>One commenter called it the “reverse centaur.”</p><p>The dream was that AI would be the centaur’s horse — we’d ride it, directing its power, multiplying our capabilities. We’d be the brains, it’d be the muscle.</p><p>Instead, we’re the cleanup crew.</p><p>Claude writes 400 lines of code in 30 seconds. Impressive. Looks right. Probably compiles. But there’s a subtle bug on line 247 where it’s comparing a string to an integer in a way that JavaScript will happily accept and silently mangle. There’s a race condition in the async handler that only manifests under load. There’s a variable named<code>data</code> that shadows another variable named<code>data</code> three scopes up.</p><p>You know. Junior developer stuff.</p><p>Except this junior developer types at 10,000 words per minute and never gets tired. So now you’re reviewing 10x more code per day, and every review requires you to maintain the mental context of code<em>you didn’t write</em>.</p><p>I spent 20 years building mental maps of codebases. Line by line. Function by function. When you write the code yourself, the map builds automatically. You know why that flag exists because you added it at 3am to fix a production incident. You know that module is haunted because you were there when the haunting began.</p><p>When Claude writes the code, you get none of that. You just get the artifact. A fully-formed thing that appeared, Athena-like, from the forehead of a language model. And you have to reverse-engineer the intent from the implementation.</p><p>This is debugging someone else’s code.</p><p>Forever.</p><h2 id="the-uncomfortable-truth"><strong>The Uncomfortable Truth</strong></h2><p>Here’s what nobody wants to say out loud: the implementation was the fun part.</p><p>Not the architecture. Architecture is meetings. Architecture is diagrams that nobody reads and Jira tickets that nobody updates. Architecture is important, yes, but it’s not<em>fun</em>.</p><p>The fun was the 2am breakthrough. The fun was that moment when the tests finally pass and you understand<em>why</em>. The fun was the flow state — that hypnotic trance where hours feel like minutes and you emerge, blinking, having built something that didn’t exist before.</p><p>LLMs took that part.</p><p>They left us the meetings.</p><h2 id="the-promoted-to-manager-cope"><strong>The “Promoted to Manager” Cope</strong></h2><p>There’s a certain cope that shows up in these discussions. “Well, actually, you’ve been promoted! Now you’re like a tech lead! You’re directing instead of doing!”</p><p>Sure. And my 2001 self was “promoted” from game programmer to accountant the moment Excel learned formulas.</p><p>Here’s the thing about being promoted: you’re supposed to<em>want</em> it. The tech leads I know who love their jobs? They love mentoring. They love the big-picture thinking. They love watching junior devs grow.</p><p>Nobody loves reviewing AI-generated code. The AI doesn’t grow. It doesn’t learn from your feedback. It just generates more code for you to review tomorrow. You’re not mentoring — you’re babysitting. And the baby has unlimited energy and zero object permanence.</p><h2 id="what-we-actually-lost"><strong>What We Actually Lost</strong></h2><p>Let me be clear: I’m not a Luddite. The productivity gains are real. I ship faster than ever. I build things in hours that would have taken weeks.</p><p>But something shifted.</p><p>When I built that platformer in 2001, I was a craftsman. Slow, inefficient, probably writing terrible code — but a craftsman. I understood my tools. I understood my materials. I understood, deeply, what I was making.</p><p>Now I’m a project manager for a very fast, very unreliable contractor.</p><p>The contractor doesn’t care about the code. It has no pride in the work. It optimizes for “looks plausible” rather than “is correct.” It will happily generate the same bug in 15 different files if you don’t catch it in the first one.</p><p>And catching it is<em>your</em> job now. Not building. Catching.</p><h2 id="the-question-nobody-wants-to-answer"><strong>The Question Nobody Wants to Answer</strong></h2><p>The Reddit thread ends with a question:</p><blockquote><p><em>“Do you miss the actual act of coding, or are you happy to just be the ‘director’ while the AI does the acting?”</em></p></blockquote><p>I think about my 2001 self. That kid who spent three weeks understanding platform scrolling. Who felt genuine joy when a rectangle moved across a screen.</p><p>Would I trade that experience for “just ask Claude to make a platformer”?</p><p>I honestly don’t know.</p><p>But I know this: that kid would be horrified by how I work today. Not impressed — horrified. Because to him, the coding<em>was</em> the point. The game was just an excuse to code.</p><p>And now the code is just an excuse to ship.</p><h2 id="the-adaptation"><strong>The Adaptation</strong></h2><p>Look, I don’t have a tidy conclusion here. The models aren’t getting worse. The productivity isn’t going away. We’re not going back to DJGPP and Allegro and three-week debugging sessions.</p><p>Maybe the joy comes back in a different form. Maybe it’s in the architecture, once we learn to love it. Maybe it’s in building the tools that build the tools. Maybe it’s in the meta-game of prompt engineering and workflow optimization.</p><p>Or maybe we just mourn quietly and move on.</p><p>I’ve gotten good at code review. I’ve built mental models for reading AI-generated code quickly, spotting the common failure modes, knowing where to look for the bugs. It’s a skill. Not the skill I wanted, but a skill.</p><p>And sometimes — rarely, but sometimes — I still drop into the code myself. Ignore Claude. Write it by hand. Feel the flow state kick in, just for a moment.</p><p>It’s slower. It’s inefficient. It’s probably a waste of time.</p><p>But that little rectangle still needs to walk across the screen sometimes. Even if nobody’s watching.</p><p><em>I help developers build, deploy, and distribute their SaaS without hiring a team. If this resonated, you might also like<a href="https://lakshminp.substack.com/p/why-your-ai-wakes-up-every-morning" rel="external nofollow noopener" class="lnp-link">Why Your AI Wakes Up Every Morning With No Memory<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</em></p>
]]></content:encoded></item><item><title>Your Code Quality Doesn't Matter Anymore (And It Never Did)</title><link>https://lakshminp.com/2026/01/code-quality-doesnt-matter/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/code-quality-doesnt-matter/</guid><category>essays</category><category>ai-coding</category><category>craft</category><description>A founder on Reddit recently shared that his CTO rebuilt what four third-party partners were providing — using Claude, in weeks, at a fraction of the cost.
Another commenter chimed in: their company replaced $300,000/year software with something they built in-house in under four months.
Meanwhile, over on r/SaasDevelopers, a developer is stuck at $200 MRR for eight months. Beautiful code. Great UX. Fifteen features. Asked where his users come from: “Uh, Product Hunt six months ago and some Reddit posts.”</description><content:encoded>&lt;![CDATA[<p>A founder on Reddit recently shared that his CTO rebuilt what four third-party partners were providing — using Claude, in weeks, at a fraction of the cost.</p><p>Another commenter chimed in: their company replaced $300,000/year software with something they built in-house in under four months.</p><p>Meanwhile, over on r/SaasDevelopers, a developer is stuck at $200 MRR for eight months. Beautiful code. Great UX. Fifteen features. Asked where his users come from: “Uh, Product Hunt six months ago and some Reddit posts.”</p><p>These two conversations are happening in parallel across the internet, and most developers haven’t connected the dots yet.</p><p>Here’s what’s actually happening: AI didn’t just make coding faster. It vaporized the feature moat entirely.</p><p><strong>The feature moat was always a lie we told ourselves.</strong></p><p>“If I build it better, they will come.” This was comforting. It meant the thing we’re good at — writing code — was the thing that mattered most.</p><p>It wasn’t true before AI. It’s aggressively not true now.</p><p>Your competitor can rebuild your core features in a weekend. Not because they’re brilliant. Because Claude is sitting right there, and the barrier to “good enough” has collapsed to basically zero. That integration you spent three months perfecting? Someone’s CTO just shipped an 80% version while you were reading this paragraph.</p><p>The YC thread frames it well: “AI mostly kills thin feature moats, not real businesses.” If your entire value proposition is “we built this thing and it works,” congratulations — you’ve built something anyone can now replicate before their coffee gets cold.</p><p><strong>So what’s actually defensible?</strong></p><p>The comments in both threads converge on the same uncomfortable answer: everything except the code.</p><p><strong>Distribution.</strong> The SaasDevelopers post makes the case bluntly: a mediocre product with great distribution beats a great product with no distribution. Every time. The OP claims $4.8K MRR with “decent features, nothing groundbreaking” because he publishes three SEO posts weekly and engages in five communities daily. His previous products had better code and failed under $500 MRR.</p><p>Whether you believe his specific numbers or not, the pattern is real. Visibility compounds. Code quality doesn’t.</p><p><strong>Operational complexity.</strong> The YC founder pivoted to payments specifically because it’s “harder to clone with AI.” Payments involve regulatory mess, edge cases that actually hurt people when you get them wrong, and trust that takes years to build. You can’t vibe-code your way to PCI compliance.</p><p><strong>Workflow embedding.</strong> One commenter nailed it: “Can a copycat ship it, but still not get adopted because switching costs and trust are the real barrier?” If yes, you might have something. If your product is a nice UI on top of an API call, you’re a feature waiting to be absorbed.</p><p><strong>Data that compounds.</strong> This one’s subtle but important. If your product gets better because you have data your competitors can’t easily replicate — user behavior, domain-specific training data, network effects — that’s a moat AI can’t trivially cross.</p><p><strong>The developer’s existential crisis.</strong></p><p>Here’s the part nobody wants to say out loud: for most technical founders, the skill that got them here is now table stakes.</p><p>You can write clean code. Great. So can Claude. You can architect systems. Wonderful. So can a junior dev with Cursor and four hours.</p><p>The skills that matter now are the ones developers historically dismissed as “marketing” or “sales” or “that stuff the business people do.”</p><p>Building an audience. Writing content that ranks. Engaging in communities without getting banned for being too promotional. Understanding what people actually want to pay for versus what’s technically impressive.</p><p>This is deeply annoying if you became a developer specifically to avoid talking to people.</p><p><strong>What to actually do.</strong></p><p>Stop adding features to a product nobody’s using. That’s not building — that’s procrastinating with a compiler.</p><p>Spend less time in your IDE and more time in the places your customers hang out. Reddit, LinkedIn, niche communities, whatever. Not to drop links. To understand what problems people are actually complaining about and whether your thing solves any of them. (It’s why I’m building<a href="https://threadhq.co/" rel="external nofollow noopener" class="lnp-link">ThreadHQ<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.)</p><p>If your product can be rebuilt in weeks with AI, either pivot to something with real operational complexity, or accept that distribution is your product now and code is just the unlock.</p><p>The YC thread suggests payments, compliance-heavy industries, anything where “mistakes actually hurt” and trust is earned over years. The SaasDevelopers thread suggests becoming a distribution machine: 20+ platform launches, daily content, systematic visibility.</p><p>Both are right. Pick your poison.</p><p><strong>The uncomfortable synthesis.</strong></p><p>AI commoditized the build. What’s left is everything around it: who knows about you, who trusts you, and how painful it would be to switch away.</p><p>The code was never the product. Now it’s just impossible to pretend otherwise.</p>
]]></content:encoded></item><item><title>The $30/Year Stack for Launching Small Bets</title><link>https://lakshminp.com/2026/01/30-dollar-saas-stack/</link><pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/30-dollar-saas-stack/</guid><category>essays</category><category>saas</category><category>kubernetes</category><description>Every time I launch a new small bet, I need the same boring stuff: professional email, a chat widget, uptime monitoring. The kind of infrastructure that’s completely unsexy but makes you look like you have your act together.
For years, I overcomplicated this. Custom SMTP servers. Self-hosted monitoring. Elaborate setups that took days to configure and broke whenever I looked at them wrong.
Then I realized something: I was spending more time on infrastructure than on validating whether anyone wanted my product.</description><content:encoded>&lt;![CDATA[<p>Every time I launch a new small bet, I need the same boring stuff: professional email, a chat widget, uptime monitoring. The kind of infrastructure that’s completely unsexy but makes you look like you have your act together.</p><p>For years, I overcomplicated this. Custom SMTP servers. Self-hosted monitoring. Elaborate setups that took days to configure and broke whenever I looked at them wrong.</p><p>Then I realized something: I was spending more time on infrastructure than on validating whether anyone wanted my product.</p><p>So I built a repeatable stack. Total cost: about $30-42 per year, per small bet. Here’s the whole thing.</p><h2 id="domain--hosting-cloudflare-free"><strong>Domain &amp; Hosting: Cloudflare (Free)</strong></h2><p>Buy your domain wherever you want, but point the nameservers to Cloudflare immediately.</p><p>Cloudflare’s free tier is absurd:</p><ul><li><p>DNS management (fast, reliable)</p></li><li><p>Free SSL certificates (automatic)</p></li><li><p>DDoS protection</p></li><li><p>CDN caching</p></li><li><p>Cloudflare Pages (unlimited sites, unlimited bandwidth)</p></li></ul><p>That last one is key. Your landing page goes on Cloudflare Pages. Connect your repo, push to main, it deploys. No servers. No bills. No thinking about infrastructure when you should be thinking about whether anyone wants your product.</p><p>I run every small bet’s landing page on CF Pages. Zero hosting cost.</p><h2 id="email-google-workspace-the-india-pricing-hack"><strong>Email: Google Workspace (The India Pricing Hack)</strong></h2><p>You want professional email.<code>hello@yourdomain.com</code>, not<code>yourdomain.help@gmail.com</code> like some kind of digital nomad running a dropshipping scam.</p><p>Google Workspace direct pricing: $6/month. Painful when you’re running multiple bets.</p><p>Google Workspace through an Indian reseller: Rs.125/month. That’s roughly $1.50.</p><p>Same product. Same Gmail experience. Same everything. Just&hellip; cheaper, because regional pricing exists and Google apparently forgot to close this loophole.</p><p>Recommended resellers: Medha Cloud, Host IT Smart, Shivaami. They’re authorized, they’re legit, and they’ll save you $50+/year per domain.</p><p>Setup takes 30 minutes: verify domain, add MX records, configure SPF/DKIM/DMARC so your emails don’t land in spam. Done.</p><h2 id="support-crisp-chat-free"><strong>Support: Crisp Chat (Free)</strong></h2><p>Intercom wants $74/month. For a small bet that might make $0.</p><p>Crisp’s free tier gives you:</p><ul><li><p>2 team seats (it’s just you anyway)</p></li><li><p>Unlimited conversations</p></li><li><p>Mobile app for notifications</p></li><li><p>A widget that doesn’t look like it was designed in 2008</p></li></ul><p>Copy-paste their script tag into your landing page. Five minutes.</p><p>Upgrade trigger: when you have so many support conversations that you need automation. Which means you have customers. Which means you can afford to pay for things.</p><h2 id="monitoring-betterstack-free"><strong>Monitoring: BetterStack (Free)</strong></h2><p>Your app will go down at 3am on a Sunday. This is not a prediction, it’s a guarantee.</p><p>BetterStack’s free tier:</p><ul><li><p>10 uptime monitors</p></li><li><p>1GB logs/month</p></li><li><p>Email and Slack alerts</p></li><li><p>3-day log retention</p></li></ul><p>Is 3-day retention enough? For a small bet you’re validating? Yes. You’re not running a bank.</p><p>Alternative: Axiom gives you 500GB ingest and 30-day retention if you’re logging more aggressively. Also free.</p><h2 id="error-tracking-sentry-free"><strong>Error Tracking: Sentry (Free)</strong></h2><p>Your code will throw exceptions in production that never happened locally. Classic.</p><p>Sentry’s free tier:</p><ul><li><p>5K errors/month</p></li><li><p>10K performance transactions</p></li><li><p>1 user</p></li><li><p>90-day retention</p></li></ul><p>For a small bet, 5K errors/month is plenty. If you’re hitting that limit, either your app is broken or you have enough users to pay for it.</p><h2 id="database-supabase-free-tier-or-self-hosted"><strong>Database: Supabase (Free Tier or Self-Hosted)</strong></h2><p>Every small bet needs a database. Supabase’s free tier is genuinely useful:</p><ul><li><p>500MB database</p></li><li><p>1GB file storage</p></li><li><p>50K monthly active users</p></li><li><p>Unlimited API requests</p></li></ul><p>That’s enough to validate most ideas. The catch: you get 2 free projects total. After that, it’s $25/month per project.</p><p>For small bets that graduate to real products, I self-host Supabase on a $6/month Hetzner VPS. Full Postgres, auth, storage, realtime — no project limits, no usage caps. (I’m building a service called<a href="https://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">Supabyoi<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> to make this dead simple. More on that soon.)</p><h2 id="the-complete-stack"><strong>The Complete Stack</strong></h2><ul><li><p><strong>Domain</strong> — ~$10-15/year</p></li><li><p><strong>Cloudflare (DNS + Pages)</strong> — Free</p></li><li><p><strong>Google Workspace (India)</strong> — ~1.50/month( 1.50/<em>month</em>( 18/year)</p></li><li><p><strong>Crisp</strong> — Free</p></li><li><p><strong>BetterStack</strong> — Free</p></li><li><p><strong>Sentry</strong> — Free</p></li><li><p><strong>Supabase</strong> — Free</p></li></ul><p><strong>Total: ~1.50/month, 1.50/<em><strong><strong>month</strong></strong></em>, 30-42/year</strong></p><p>That’s DNS, hosting, professional email, live chat, uptime monitoring, error tracking, and a database for less than a single month of most “startup” tools.</p><h2 id="the-rules"><strong>The Rules</strong></h2><p><strong>Don’t upgrade until you have paying customers.</strong> Free tiers exist for validation. Use them.</p><p><strong>Keep the setup identical across bets.</strong> Same tools, same patterns, same DNS records. You should be able to launch a new bet’s infrastructure in an afternoon, not a weekend.</p><p><strong>Resist the urge to self-host.</strong> Yes, you<em>can</em> run your own mail server. You can also perform your own dental surgery. Neither is advisable.</p><h2 id="when-to-actually-upgrade"><strong>When To Actually Upgrade</strong></h2><ul><li><p><strong>Google Workspace</strong> — You need &gt;30GB storage → $7/mo</p></li><li><p><strong>Crisp</strong> — You need chatbots or &gt;2 team members → $25/mo</p></li><li><p><strong>BetterStack</strong> — You’re pushing &gt;1GB logs/month → $24/mo</p></li><li><p><strong>Sentry</strong> — You’re hitting 5K errors/month → $26/mo</p></li><li><p><strong>Supabase</strong> — You need &gt;2 projects or more storage → $25/mo (or self-host)</p></li></ul><p>Notice a pattern? These are all “you have real traction” problems. Good problems to have.</p><h2 id="whats-not-covered-yet"><strong>What’s Not Covered (Yet)</strong></h2><p>This is the skeleton — the basic infrastructure every small bet needs from day one.</p><p>I’ll cover these in separate posts:</p><ul><li><p><strong>Tech stack choices</strong> (frameworks, languages, deployment)</p></li><li><p><strong>Payment processing</strong> (Stripe, Lemon Squeezy, regional considerations)</p></li><li><p><strong>CI/CD pipelines</strong> (GitHub Actions, deployment automation)</p></li><li><p><strong>Landing page patterns</strong> (what actually converts)</p></li></ul><p>One thing at a time.</p><h2 id="the-point"><strong>The Point</strong></h2><p>Infrastructure should be invisible. It should cost almost nothing while you’re validating. It should scale up only when you have revenue to pay for it.</p><p>$30/year per bet means you can run 10 small bets for less than most people pay for a single Notion subscription.</p><p>Stop building infrastructure. Start shipping products.</p><hr><p><em>This is part of my “Deploy” series — simple infrastructure patterns for solo operators who’d rather build products than manage servers.</em></p>
]]></content:encoded></item><item><title>90% of Programming Skills Just Got Commoditized. The Other 10% Is Worth 1000X More.</title><link>https://lakshminp.com/2026/01/programming-skills-commoditized/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/programming-skills-commoditized/</guid><category>essays</category><category>ai-coding</category><category>craft</category><description>Andrej Karpathy recently wrote something ↗ that’s been rattling around my head:
“I’ve never felt this much behind as a programmer. The profession is being dramatically refactored as the bits contributed by the programmer are increasingly sparse and between. I have a sense that I could be 10X more powerful if I just properly string together what has become available over the last year and a failure to claim the boost feels decidedly like skill issue.”</description><content:encoded>&lt;![CDATA[<p>Andrej Karpathy recently<a href="https://x.com/karpathy/status/2004607146781278521" rel="external nofollow noopener" class="lnp-link">wrote something<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> that’s been rattling around my head:</p><blockquote><p>“I’ve never felt this much behind as a programmer. The profession is being dramatically refactored as the bits contributed by the programmer are increasingly sparse and between. I have a sense that I could be 10X more powerful if I just properly string together what has become available over the last year and a failure to claim the boost feels decidedly like skill issue.”</p></blockquote><p>He then listed what this new layer looks like: agents, subagents, prompts, contexts, memory, modes, permissions, tools, plugins, skills, hooks, MCP, LSP, slash commands, workflows, IDE integrations.</p><p>His conclusion: “Clearly some powerful alien tool was handed around except it comes with no manual and everyone has to figure out how to hold it and operate it, while the resulting magnitude 9 earthquake is rocking the profession.”</p><p>I felt this in my bones.</p><h2 id="the-old-stack-vs-the-new-stack"><strong>The Old Stack vs The New Stack</strong></h2><p>The old programming stack was hard enough:</p><p>Hardware -&gt; OS -&gt; Language -&gt; Frameworks -&gt; Your Code</p><p>Years of learning. Layers of abstraction. But at least it was<em>deterministic</em>. At least there were manuals. At least Stack Overflow had answers.</p><p>The new stack adds a layer on top:</p><p>You -&gt; Prompts/Agents/Context/Memory/Tools/Modes -&gt; Code</p><p>This layer is fundamentally different. It’s stochastic. It’s fallible. It’s unintelligible. And it changes every few weeks.</p><p>There’s no certification. There’s no textbook. There’s no “Effective AI Orchestration” by Joshua Bloch. Just a bunch of people figuring it out in Discord servers and sharing CLAUDE.md files and best practices like trading cards.</p><h2 id="the-divide-is-already-here"><strong>The Divide Is Already Here</strong></h2><p>Someone on Reddit<a href="https://reddit.com/r/ClaudeAI/comments/1lquetd/the_claude_code_divide_those_who_know_vs_those/" rel="external nofollow noopener" class="lnp-link">described the pattern<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> they’re seeing on their team:</p><blockquote><p>“Two developers with similar experience working on similar tasks, but one consistently ships features in hours while the other is still debugging. At first I thought it was just luck or skill differences. Then I realized what was actually happening — it’s their instruction library.”</p></blockquote><p>They’re watching an underground collection of power users share workflows like secrets:</p><ul><li><p>Commands that automatically debug entire codebases</p></li><li><p>CLAUDE.md files that turn Claude into domain experts</p></li><li><p>Slash commands that turn 45-minute processes into 2-minute ones</p></li></ul><p>Meanwhile, most people are still typing “help me fix this bug” and wondering why their results suck.</p><p>As one developer put it: “The differences between someone who opens up CC for the first time and someone with tuned md files is beyond night and day.”</p><h2 id="the-skill-issue-is-real-but-not-the-one-you-think"><strong>The Skill Issue Is Real (But Not The One You Think)</strong></h2><p>Here’s what hit me about Karpathy’s framing: he called it a “skill issue.”</p><p>Not a tools issue. Not an access issue. Not a funding issue.</p><p>A<em>skill</em> issue.</p><p>The 10X boost exists. The leverage is real. But claiming it requires mastering something that didn’t exist two years ago and has no curriculum.</p><p>Someone in that same thread nailed the uncomfortable truth: “90% of traditional programming skills are becoming commoditized while the remaining 10% becomes worth 1000x more. That 10% isn’t coding — it’s knowing how to architect AI workflows.”</p><p>The irony is brutal. We spent years mastering syntax, frameworks, design patterns. Now an AI can generate all of that in seconds. What it<em>can’t</em> do is orchestrate itself effectively. That’s your job now.</p><h2 id="what-the-new-layer-actually-looks-like"><strong>What The New Layer Actually Looks Like</strong></h2><p>Let me make this concrete. Here’s what I’ve had to learn in the past year that wasn’t part of any CS curriculum:</p><p><strong>CLAUDE.md Architecture</strong></p><p>Your instructions file isn’t documentation. It’s programming. The structure, the phrasing, what you include vs exclude — these decisions compound across every interaction. A well-architected CLAUDE.md is worth more than a well-architected codebase.</p><p><strong>Context Management</strong></p><p>Every token matters. MCP servers eat context. Long conversations drift. You need to think about what Claude knows, what it’s forgotten, when to compact, when to start fresh. It’s memory management, but for a mind that isn’t yours.</p><p><strong>Prompt Design</strong></p><p>Not “prompt engineering” in the LinkedIn-influencer sense. Actual design. When do you give examples? When do you constrain? When do you let it explore? How do you phrase things so it doesn’t hallucinate? How do you trigger deeper thinking? These are learnable skills with massive payoff differences.</p><p><strong>Tool Orchestration</strong></p><p>MCP, skills, hooks, slash commands. Which tool for which job? When does an MCP server make sense vs a bash script vs a skill file? How do you chain them? How do you debug when the chain breaks?</p><p><strong>Mode Awareness</strong></p><p>Plan mode vs implement mode. When to let Claude explore vs when to constrain. When to use subagents. When to go linear. The<em>meta</em> of working with AI — knowing when to switch approaches — is itself a skill.</p><p><strong>Verification Choreography</strong></p><p>AI generates fast. Verification is the bottleneck. How do you structure your workflow so you’re not just rubber-stamping garbage? How do you catch the 8 production bombs before they ship? (Yes,<a href="https://lakshminp.substack.com/p/what-claude-cant-do-for-you" rel="external nofollow noopener" class="lnp-link">I wrote about this<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.)</p><h2 id="the-manual-that-doesnt-exist"><strong>The Manual That Doesn’t Exist</strong></h2><p>An older developer on Reddit<a href="https://reddit.com/r/ClaudeAI/comments/1lquetd/the_claude_code_divide_those_who_know_vs_those/" rel="external nofollow noopener" class="lnp-link">captured the frustration<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>:</p><blockquote><p>“I started using AI about 2 years ago. I thought I was doing good, but then I started seeing all this stuff about MCP servers, md files etc and I am kind of lost. I want to learn more and I want to improve my AI skills but it’s difficult for me.”</p></blockquote><p>This is someone with decades of experience, feeling lost because the new layer has no onramp.</p><p>The manual doesn’t exist because the platform keeps shifting. Claude Code ships updates weekly. New features appear. Old patterns stop working. The MCP ecosystem is exploding. Skills just launched. Hooks changed. The ground won’t stop moving.</p><p>You can’t study for an earthquake. You can only practice surfing.</p><h2 id="how-im-learning-imperfectly"><strong>How I’m Learning (Imperfectly)</strong></h2><p>I don’t have this figured out. Nobody does. But here’s what’s working:</p><p><strong>Steal shamelessly</strong>. Find people who are clearly more productive and reverse-engineer their setup. Their CLAUDE.md files, their slash commands, their workflows. GitHub repos, Discord servers, Reddit threads. The good stuff is scattered but findable.</p><p><strong>Treat your setup as code</strong>. Version control your CLAUDE.md. Iterate on your slash commands. When something works, document why. When something fails, autopsy it. Your instruction library is a codebase now.</p><p><strong>Invest in meta-skills</strong>. The specific tools will change. MCP might get replaced. Claude Code might get competition. But the meta-skills — context management, prompt design, verification choreography — those transfer.</p><p><strong>Actually use the new features</strong>. Hooks exist. Subagents exist. Skills exist. Most people ignore them because they’re “advanced.” They’re not advanced. They’re just new. The learning curve is the moat.</p><p><strong>Teach to learn</strong>. Writing about this forces me to understand it. Explaining my setup to others reveals the gaps. The best way to master the new layer is to articulate it.</p><h2 id="the-uncomfortable-conclusion"><strong>The Uncomfortable Conclusion</strong></h2><p>Karpathy is right. There’s a 10X boost available. Failing to claim it is a skill issue.</p><p>But it’s a<em>new</em> skill. One that didn’t exist before. One that has no manual, no certification, no clear path.</p><p>The people figuring it out are building compound advantages. Every custom command, every refined CLAUDE.md pattern, every workflow optimization — it all stacks. The gap between those who master the new layer and those who don’t is widening fast.</p><p>The earthquake is still happening. The alien tool is still being figured out. The manual is being written in real-time by the people using it. And the rules are being changed as we speak/code/write.</p><p>Roll up your sleeves.</p><hr><p><em>This is a companion to my previous essay on<a href="https://lakshminp.substack.com/p/claude-code-is-incredible-it-also" rel="external nofollow noopener" class="lnp-link">what Claude can’t do for you<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. That one covered the old skills that still matter. This one covers the new skills you need to add.</em></p>
]]></content:encoded></item><item><title>Claude Code Is Incredible. It Also Almost Shipped 8 Production Bombs Last Week.</title><link>https://lakshminp.com/2026/01/claude-code-production-bombs/</link><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/claude-code-production-bombs/</guid><category>essays</category><category>claude-code</category><description>What 15 years of production scars are still good for Last week I caught eight bugs across three projects. Not typos. Not missing semicolons. Real, ship-breaking, production-melting problems that would have sailed right past code review and into the waiting arms of actual users.
Claude Code wrote the code. Claude Code passed the tests. Claude Code would have happily deployed it.
And Claude Code had no idea anything was wrong.</description><content:encoded>&lt;![CDATA[<h2 id="what-15-years-of-production-scars-are-still-good-for"><strong>What 15 years of production scars are still good for</strong></h2><p>Last week I caught eight bugs across three projects. Not typos. Not missing semicolons. Real, ship-breaking, production-melting problems that would have sailed right past code review and into the waiting arms of actual users.</p><p>Claude Code wrote the code. Claude Code passed the tests. Claude Code would have happily deployed it.</p><p>And Claude Code had no idea anything was wrong.</p><p>I’ve written about<a href="https://lakshminp.substack.com/p/the-invisible-tax-you-pay-when-you" rel="external nofollow noopener" class="lnp-link">comprehension debt<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> and argued that<a href="https://lakshminp.substack.com/p/clean-code-is-dead-long-live-clean" rel="external nofollow noopener" class="lnp-link">clean code is dead<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. But here’s the uncomfortable third act: even if you understand your specs perfectly and verify your outcomes ruthlessly, there’s a whole category of knowledge that AI simply doesn’t have.</p><p>Call it production intuition. Call it battle scars. Call it “I’ve been burned by this exact thing before.”</p><p>Whatever you call it, you can’t prompt your way into it.</p><h3 id="the-localhost-delusion"><strong>The Localhost Delusion</strong></h3><p>Here’s what AI is optimized for: making code that works on your machine, right now, with your current data, under ideal conditions.</p><p>Here’s what AI is catastrophically bad at: imagining your code running on three replicas behind a load balancer at 3am when the database is under pressure and someone’s running a batch job that nobody documented.</p><p>A Fortune 100 developer on Reddit<a href="https://reddit.com/r/ExperiencedDevs/comments/1mg2r6y/the_era_of_ai_slop_cleanup_has_begun/" rel="external nofollow noopener" class="lnp-link">put it bluntly<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>: “There’s a lot of vibe coded slop that works well for MVP but will absolutely fall apart under stress and within production environments once they scale to more users. It doesn’t really reveal itself until later when it’s much more difficult to fix.”</p><p>Later. When it’s difficult. The horror.</p><p>Let me walk you through what “later” looked like for me this week.</p><h3 id="pattern-1-production-blindness"><strong>Pattern 1: Production Blindness</strong></h3><p><strong>The Concurrency Landmine</strong></p><p>I’m building a tool that routes MCP calls. Claude wrote it in Python — clean, well-structured, exactly what I asked for. Worked beautifully in testing. One request, one response, everybody’s happy.</p><p>Then I imagined what happens when three Claude Code sessions hit it simultaneously.</p><p>Oh.</p><p><em>Oh no.</em></p><p>Python’s threading model and the phrase “parallel requests” get along about as well as cats and bathtubs. Claude’s solution? More Python. Refactor this, optimize that. Very confident. Would’ve worked for lower concurrency.</p><p>But I knew this thing needed to handle dozens of parallel sessions. I prompted it to rewrite in Go. Claude nailed the port — goroutines, channels, the works. Problem solved in an afternoon.</p><p>The code was excellent. The language selection wasn’t. Claude optimizes brilliantly within the box you give it. It just won’t question whether you’re in the right box.</p><p><strong>The Replicas Problem</strong></p><p>Auth rate limiting. Claude implemented it in-memory with a clean sliding window algorithm. Textbook correct. Tests pass. Ship it.</p><p>One replica: perfect.<br>
Two replicas: every user gets double the rate limit.<br>
Three replicas: chaos.</p><p>This is distributed systems 101. The kind of thing you learn after you’ve been paged at 2am because someone figured out they could hit your API from three different IPs and get 3x the rate limit.</p><p>When I pointed this out, Claude immediately suggested Redis-backed rate limiting with proper distributed locking. Great solution. But it didn’t think about replicas until I did. Claude builds for the deployment model you describe. If you don’t describe it, localhost is the default.</p><p><strong>The Sync Task Landmine</strong></p><p>An API endpoint runs a long-running task. Claude implemented it synchronously — straightforward, easy to understand, does exactly what the tests verify.</p><p>Deploy it. First user clicks the button. 30-second timeout. 504 Gateway Timeout.</p><p>When I explained the problem, Claude refactored it to Celery with proper task queuing, retry logic, and status polling. Solid implementation. Took maybe 20 minutes.</p><p>But here’s the thing: I only caught it because I tested the actual user flow, not just the unit tests. Claude implemented what I asked for. I didn’t ask for “an endpoint that won’t timeout in production.” That’s on me. But it’s also the kind of thing I’ve learned to check after watching synchronous endpoints die approximately 47 times.</p><h3 id="pattern-2-ecosystem-amnesia"><strong>Pattern 2: Ecosystem Amnesia</strong></h3><p><strong>The Deprecation You Won’t Find on Stack Overflow</strong></p><p>Supabase deprecated their API keys. Not in a big announcement. Not in the docs you’d naturally read. In a<a href="https://github.com/orgs/supabase/discussions/29260" rel="external nofollow noopener" class="lnp-link">GitHub discussion<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> with 43 comments and a lot of confused developers.</p><p>I found out because I read the discussion. I then had to fix three separate projects.</p><p>Claude doesn’t read GitHub discussions. Claude doesn’t know what the community is grumbling about. Claude’s knowledge is frozen in time, and the ecosystem keeps moving.</p><p><strong>The “Works But Wrong” Framework Choice</strong></p><p>Claude encrypted secrets at rest using Fernet keys. Technically correct. Cryptographically sound. Tests pass. Secure enough.</p><p>But I’m using Supabase. Supabase has a vault feature built specifically for this. When I mentioned it, Claude migrated everything over cleanly — proper RLS policies, the works.</p><p>The Fernet implementation wasn’t<em>wrong</em>. It just wasn’t the<em>right</em> choice for this stack. Supabase vault means one less thing I manage, one less key rotation I handle, one less piece of infrastructure to think about.</p><p>Claude doesn’t know the zeitgeist of “what’s the idiomatic way to do this in Supabase.” That knowledge lives in community forums, Discord servers, and the muscle memory of people who’ve shipped Supabase apps before.</p><p><strong>The Build System That Wasn’t</strong></p><p>I made UI mockups with Tailwind CSS(using Claude, to be fair). Told Claude to use them. Claude happily served Tailwind from a CDN.</p><p>In development? Fine.<br>
In production? Every page load fetches the entire Tailwind library. Uncompiled. Unoptimized. Approximately 300KB of CSS you don’t need.</p><p>Claude knows what Tailwind is. Claude doesn’t know that real projects compile it. That’s the kind of tribal knowledge you pick up by shipping things and watching your Lighthouse scores crater.</p><h3 id="pattern-3-verification-vacuum"><strong>Pattern 3: Verification Vacuum</strong></h3><p><strong>The Cache That Wasn’t</strong></p><p>Database queries were getting slow. I asked Claude to add a caching layer. Claude wrote a beautiful Redis caching module — proper TTLs, cache invalidation on writes, the works. Tests for the module passed. I shipped it, watched the deployment go green, felt the warm glow of productivity.</p><p>The cache wasn’t being hit.</p><p>Claude built an excellent caching module. Claude did not check whether the actual query functions were<em>calling</em> the cache. The module worked perfectly. Nothing was using it. Every request still hit the database directly.</p><p>I discovered this by checking Redis after a few hundred requests. Empty. Revolutionary debugging technique, I know.</p><p>Could tests have caught this? Sure. Integration tests that verify “when I make this API call, Redis gets a cache entry.” But I’d need to know to write that test. Claude wrote unit tests for the caching module. I should have asked for end-to-end verification. The knowledge that “modules can exist without being properly wired up” is experience. Pattern recognition. The scar tissue from shipping features that weren’t actually features before.</p><h3 id="pattern-4-architectural-judgment"><strong>Pattern 4: Architectural Judgment</strong></h3><p><strong>The Multitenancy Time Bomb</strong></p><p>A project using Qdrant vector database. Users store embeddings. Multiple users. Shared infrastructure.</p><p>The question that should wake you up at night: Can User A see User B’s data?</p><p>Claude’s implementation used collection-level isolation with proper tenant IDs in the filter queries. Reasonable approach. Worked in my tests.</p><p>But a thorough multitenancy review? The kind where you trace every query path, every edge case, every possible way data could leak between tenants? Where you think about what happens if someone forgets to pass the tenant filter? Where you consider whether the default behavior is secure-by-default or insecure-by-default?</p><p>That review took me two hours. Claude can help<em>execute</em> the fixes I identify, but it won’t spontaneously think “hey, multitenancy is a critical architectural decision that deserves paranoid scrutiny.”</p><p>Get multitenancy wrong and you’re on the front page of Hacker News, and not in the good way. Claude builds features. You build threat models.</p><h3 id="what-the-discourse-gets-wrong"><strong>What The Discourse Gets Wrong</strong></h3><p>Here’s what bothers me about the AI discourse: both sides are missing the point.</p><p>The AI skeptics say “AI code is garbage, don’t use it.” That’s wrong. Claude Code is incredibly useful. I ship faster. I handle complexity I couldn’t handle alone. The leverage is real.</p><p>The AI evangelists say “AI will replace developers, just describe what you want.” That’s also wrong. Describing what you want is the<em>easy</em> part. Knowing what you<em>should</em> want — that’s where the experience lives.</p><p>Someone on r/ExperiencedDevs<a href="https://reddit.com/r/ExperiencedDevs/comments/1on84au/ai_wont_make_coding_obsolete_coding_isnt_the_hard/" rel="external nofollow noopener" class="lnp-link">nailed it<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>: “Coding is the boring/easy part. Typing is just transcribing decisions into a machine. The real work is upstream: understanding what’s needed, resolving ambiguity, negotiating tradeoffs, and designing coherent systems.”</p><p>The developers who thrive aren’t the ones who write the most code. They’re the ones who catch the multitenancy bug before it ships. Who know that in-memory rate limiting won’t scale. Who’ve been burned by synchronous endpoints and CDN-served CSS and proxies that aren’t wired up.</p><p>Experience isn’t knowing the syntax. Experience is knowing the failure modes.</p><h3 id="whats-actually-worth-learning"><strong>What’s Actually Worth Learning</strong></h3><p>So what’s worth learning when AI can write the code?</p><p><strong>Production Thinking</strong></p><p>This is the big one. Every example above comes back to it: Claude builds for localhost, you build for production.</p><p>Concretely, this means developing instincts for questions like:</p><ul><li><p>“What happens when there are multiple replicas?” (Rate limiting, session state, caching, file storage — anything in-memory becomes a distributed systems problem)</p></li><li><p>“What happens under load?” (Synchronous operations become timeouts. N+1 queries become database meltdowns. That “fast enough” endpoint becomes a bottleneck)</p></li><li><p>“What happens when dependencies fail?” (Database is slow. External API is down. Redis is unreachable. Do you degrade gracefully or explode?)</p></li><li><p>“What happens at 3am when nobody’s watching?” (Background jobs. Retry logic. Dead letter queues. The things that fail silently)</p></li></ul><p>How do you learn this? You can’t shortcut it. You deploy things. You watch them break. You read post-mortems. You get paged. You develop a paranoid imagination for failure modes.</p><p>But you can accelerate it: before you ship, spend 10 minutes imagining the deployment. Draw the boxes. How many instances? What’s in front of them? Where’s the state? What’s shared? This exercise catches 80% of the issues I described above.</p><p><strong>Ecosystem Intuition</strong></p><p>This is knowing the<em>zeitgeist</em> of your stack — not just what’s possible, but what’s idiomatic. What the community actually uses. What’s deprecated but still in the docs. What’s new but not proven.</p><p>Concretely:</p><ul><li><p>Read the GitHub discussions, not just the docs. That’s where deprecations get announced, migration paths get debated, and footguns get documented.</p></li><li><p>Follow the maintainers on Twitter/X or Bluesky. They’ll tell you about breaking changes before the docs catch up.</p></li><li><p>Lurk in Discord servers. The “how should I do X” discussions reveal what’s considered best practice.</p></li><li><p>Actually ship with the stack. The difference between “I’ve read about Supabase” and “I’ve shipped three apps with Supabase” is enormous.</p></li></ul><p>The goal: when Claude suggests an approach, you can immediately sense whether it’s the “right” way or just “a” way. Fernet encryption vs Supabase vault. CDN Tailwind vs compiled Tailwind. Redis rate limiting vs in-memory rate limiting. These aren’t in the documentation. They’re in the collective experience.</p><p><strong>Architectural Paranoia</strong></p><p>Some decisions are easy to change later. Some aren’t. Knowing the difference is half of senior engineering.</p><p>The ones that are hard to reverse:</p><ul><li><p><strong>Multitenancy model</strong>: Shared database with tenant IDs? Separate schemas? Separate databases? Choose wrong and you’re rewriting everything.</p></li><li><p><strong>Auth architecture</strong>: Where do tokens live? How do sessions work? What’s the refresh flow? Changing this later breaks every client.</p></li><li><p><strong>Data model fundamentals</strong>: Relational vs document. Normalized vs denormalized. Adding a column is easy. Restructuring your entire data model is not.</p></li><li><p><strong>API contract design</strong>: Once clients depend on your response shape, changing it is a versioning nightmare.</p></li></ul><p>For each of these, Claude will happily implement whatever you ask. It won’t stop and say “are you sure about this? This is hard to change later.” That paranoia is your job.</p><p>My rule: for any architectural decision I can’t easily reverse, I spend at least an hour thinking about alternatives before I let Claude write the first line.</p><p><strong>Verification Instincts</strong></p><p>What should you test for? What’s easy to get wrong? What<em>looks</em> done but isn’t actually wired up?</p><p>This is pattern recognition from past failures. The cache that wasn’t being hit. The feature flag that was never checked. The error handler that swallowed exceptions silently.</p><p>Concretely:</p><ul><li><p><strong>Test the user flow, not just the units</strong>. My caching module passed all its unit tests. The integration was broken. If I’d tested “make this API call and verify Redis has an entry,” I’d have caught it immediately.</p></li><li><p><strong>Verify your assumptions</strong>. Claude wrote the code, but did the code actually get<em>used</em>? Add a log line. Check the network tab. Confirm reality matches intention.</p></li><li><p><strong>Break it on purpose</strong>. What happens when you pass invalid input? What happens when the database is slow? What happens when the auth token is expired? Claude tests the happy path. You test the sad path.</p></li></ul><p>The underlying skill: developing a checklist of “things that can look done but aren’t” for your specific domain. Every time you get burned, add it to the list. Eventually, you check these instinctively.</p><h3 id="the-uncomfortable-conclusion"><strong>The Uncomfortable Conclusion</strong></h3><p>A freelance developer with 8 years of experience<a href="https://reddit.com/r/ExperiencedDevs/comments/1mg2r6y/the_era_of_ai_slop_cleanup_has_begun/" rel="external nofollow noopener" class="lnp-link">described a pattern<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> he’s seeing across multiple clients: companies paying good money for internal software that barely works. Same symptoms every time. AI-generated comments. Algorithms that make no sense. Inconsistent patterns.</p><p>“Yes it mostly works,” he wrote, “but does so terribly to the point where it needs to be fixed.”</p><p>The era of AI slop cleanup has begun. And the people doing the cleanup are the ones who know what production actually looks like.</p><p>Claude builds for localhost. You build for production.</p><p>That gap is where your fifteen years live. And it’s not getting smaller.</p><hr><p><em>This is the third essay in an accidental trilogy. First:<a href="https://lakshminp.substack.com/p/the-invisible-tax-you-pay-when-you" rel="external nofollow noopener" class="lnp-link">comprehension debt is real<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. Second:<a href="https://lakshminp.substack.com/p/clean-code-is-dead-long-live-clean" rel="external nofollow noopener" class="lnp-link">clean code is dead<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. This one: the skills that matter more now, not less.</em></p>
]]></content:encoded></item><item><title>Clean Code Is Dead. Long Live Clean Specs.</title><link>https://lakshminp.com/2026/01/clean-code-dead-clean-specs/</link><pubDate>Fri, 09 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/clean-code-dead-clean-specs/</guid><category>essays</category><category>ai-coding</category><category>craft</category><description>Steve Yegge shipped 225,000 lines of Go code he’s never read.
Let that sink in.
Beads ↗ — his coding agent memory system — is used by tens of thousands of developers daily. It’s 100% vibe coded. Yegge has never looked at a single line. Same with his new project, Gastown ↗. Three weeks old, 100% vibe coded, never seen the code, never plans to.</description><content:encoded>&lt;![CDATA[<p>Steve Yegge shipped 225,000 lines of Go code he’s never read.</p><p>Let that sink in.</p><p><a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a" rel="external nofollow noopener" class="lnp-link">Beads<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> — his coding agent memory system — is used by tens of thousands of developers daily. It’s 100% vibe coded. Yegge has never looked at a single line. Same with his new project,<a href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04" rel="external nofollow noopener" class="lnp-link">Gastown<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>. Three weeks old, 100% vibe coded, never seen the code, never plans to.</p><p>His reaction to anyone uncomfortable with this? “Get out now.”</p><h2 id="the-heresy"><strong>The Heresy</strong></h2><p>For two decades, we’ve been taught that code is literature. Uncle Bob’s Clean Code. Martin Fowler’s Refactoring. Elegant variable names. Single responsibility. Code should read like prose.</p><p>We optimized for human comprehension because humans had to maintain it.</p><p>But what if that’s no longer true?</p><p><a href="https://www.simonhoiberg.com/" rel="external nofollow noopener" class="lnp-link">Simon Hoiberg<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> put it bluntly: “Half my code is now written by AI, and the other half is read by AI to fix bugs. Optimizing for human readability is becoming pointless.”</p><p>The audience for your code has changed. And it’s not you anymore.</p><h2 id="the-other-day-i-wrote-about-comprehension-debt"><strong>The Other Day I Wrote About Comprehension Debt</strong></h2><p>I argued that vibe coding creates legacy code from day one. That velocity without comprehension isn’t velocity — it’s procrastination with extra steps.</p><p>I still believe that. Mostly.</p><p>But here’s the uncomfortable follow-up question: What if comprehension debt only matters when<em>you</em> have to pay it?</p><p>If AI writes the code and AI debugs the code and AI refactors the code&hellip; who exactly needs to understand it?</p><h2 id="the-new-contract"><strong>The New Contract</strong></h2><p>The old contract: Write clean code so humans can read it.</p><p>The new contract: Write code that produces correct outcomes, verified by tests that humans can understand.</p><p>This is a crucial shift. The code becomes disposable infrastructure. The tests become the spec. The behavior becomes the product.</p><p>Steve Yegge doesn’t need to understand 225,000 lines of Go. He needs to understand what Beads should do. The tests verify that it does it. The code is just&hellip; implementation detail. An artifact. A byproduct.</p><h2 id="clean-specs--clean-code"><strong>Clean Specs &gt; Clean Code</strong></h2><p>Here’s the heretical thought experiment:</p><p>What if “clean code” principles should now apply to your specifications instead of your source code?</p><p>Think about it:</p><ul><li><p><strong>Readable intent</strong>: Your specs should be crystal clear. “Users can checkout with valid payment. Invalid cards show an error. Empty carts can’t checkout.”</p></li><li><p><strong>Single responsibility</strong>: Each spec describes one behavior. Not implementation — behavior.</p></li><li><p><strong>Self-documenting</strong>: Specs are the documentation that gets executed. They describe what the system should do, and you verify it actually does.</p></li><li><p><strong>Easy to modify</strong>: When requirements change, you update the spec first. AI updates everything else.</p></li></ul><p>The source code can be a tangled mess of AI-generated spaghetti. Who cares? If you can clearly specify what you want and verify you got it, the implementation is just a detail.</p><h2 id="the-yegge-paradox"><strong>The Yegge Paradox</strong></h2><p>Here’s what’s wild. In the Vibe Coding book Yegge co-authored with Gene Kim, “Steve” is described as reviewing 10,000 lines of code a day, throwing away 10 lines for every line kept.</p><p>Wait. He reviews code? I thought he never looks at it?</p><p>The answer, I think, is this: He reviews<em>outcomes</em>. He reviews test results. He reviews whether the thing works. He’s not reading code for elegance or comprehension. He’s running it, breaking it, verifying it.</p><p>The code review has become a behavior review.</p><h2 id="what-this-means-for-you"><strong>What This Means for You</strong></h2><p>I’m not saying burn your Clean Code book. (Okay, maybe I am. That thing is 400 pages of what could’ve been a blog post.)</p><p>But consider this workflow:</p><ol><li><p><strong>Specify the behavior</strong> — in plain language. “Users can checkout with valid payment. Invalid cards show an error. Empty carts can’t checkout.”</p></li><li><p><strong>Let AI write the tests</strong> — it turns your specs into executable verification</p></li><li><p><strong>Let AI write the implementation</strong> — who cares if it’s ugly</p></li><li><p><strong>Verify the outcomes</strong> — does it do what you specified? Try to break it. Edge cases covered?</p></li><li><p><strong>Ship it</strong> — the code is a means to an end</p></li></ol><p>If something breaks, you don’t debug the code. You describe the broken behavior. AI writes a failing test. AI fixes the implementation. You verify the outcome. You never had to understand the implementation. You just had to understand what you wanted.</p><h2 id="the-catch"><strong>The Catch</strong></h2><p>There’s always a catch.</p><p>This only works if your specifications are actually good. If your specs are vague, incomplete, missing edge cases — you’re in the worst of both worlds. Incomprehensible code that doesn’t even do what you need.</p><p>That’s not vibe coding. That’s vibes-all-the-way-down coding. And that’s how you get 18 out of 20 CTOs reporting production disasters.</p><p>The discipline has to go somewhere. If you’re not putting it into clean code, you damn well better be putting it into clear specifications and ruthless outcome verification.</p><h2 id="the-real-skill-shift"><strong>The Real Skill Shift</strong></h2><p>Old skill: Writing elegant, maintainable code that other humans can understand.</p><p>New skill: Specifying behavior precisely and verifying outcomes ruthlessly.</p><p>The developers who thrive won’t be the ones who write the cleanest code. They’ll be the ones who can articulate exactly what they want. Who can break their own systems. Who can look at a feature and immediately think of ten ways it could fail.</p><p>Code literacy is becoming specification literacy. The new “clean code” is clear intent.</p><h2 id="the-uncomfortable-conclusion"><strong>The Uncomfortable Conclusion</strong></h2><p>We spent twenty years optimizing for human readers who are increasingly being replaced by AI readers.</p><p>Maybe Steve Yegge is right. Maybe the code doesn’t matter. Maybe it never really mattered — we just didn’t have anything better.</p><p>What matters is: Does it work? Can you prove it? Can you verify it still works after changes?</p><p>Clean code was a proxy for those questions. A good heuristic when humans had to debug.</p><p>Clean specs answer those questions directly.</p><p>The code is dead. Long live the specs.</p><hr><p><em>This is a follow-up to my<a href="https://lakshminp.substack.com/p/the-invisible-tax-you-pay-when-you" rel="external nofollow noopener" class="lnp-link">recent essay<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> on comprehension debt. The tension is real: you need to understand the problem deeply enough to specify it clearly, but maybe not the implementation at all. Where that line is&hellip; I’m still figuring out.</em></p>
]]></content:encoded></item><item><title>I Found a Cryptominer in My Client's Production Cluster. Claude Code Found the Attacker.</title><link>https://lakshminp.com/2026/01/cryptominer-production-cluster/</link><pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/cryptominer-production-cluster/</guid><category>essays</category><category>kubernetes</category><category>claude-code</category><description>New Year’s Day. Coffee in hand. Ready to ease back into work.
Then I saw the logs.
2026-01-02T06:34:27 GET xmrig-6.24.0-linux-static-x64.tar.gz 2026-01-02T06:34:30 GET http://37.32.6.33:7979/m 2026-01-02T06:34:30 spawn /opt/systemf/m ENOENT xmrig. In production. Someone was mining Monero on my client’s Kubernetes cluster.
The horror.
The Investigation I had a few hundred megabytes of JSON logs and approximately zero patience for manually correlating timestamps. So I did what any reasonable person would do: I asked Claude Code to analyze the logs and figure out what triggered the miner download.</description><content:encoded>&lt;![CDATA[<p>New Year’s Day. Coffee in hand. Ready to ease back into work.</p><p>Then I saw the logs.</p><pre><code>2026-01-02T06:34:27 GET xmrig-6.24.0-linux-static-x64.tar.gz
2026-01-02T06:34:30 GET http://37.32.6.33:7979/m
2026-01-02T06:34:30 spawn /opt/systemf/m ENOENT</code></pre><p>xmrig. In production. Someone was mining Monero on my client’s Kubernetes cluster.</p><p>The horror.</p><h2 id="the-investigation"><strong>The Investigation</strong></h2><p>I had a few hundred megabytes of JSON logs and approximately zero patience for manually correlating timestamps. So I did what any reasonable person would do: I asked Claude Code to analyze the logs and figure out what triggered the miner download.</p><p>Within seconds, it built a timeline:</p><p><strong>Time Event</strong></p><p>06:34:26. Normal request to /onboarding</p><p>06:34:27. xmrig downloaded from GitHub</p><p>06:34:30. Secondary payload from sketchy IP</p><p>06:34:57. Container OOMKilled</p><p>The cryptominer was so resource-hungry it consumed 2GB of memory in 30 seconds and crashed the container. Ironic. The attacker’s greed saved us from a prolonged compromise.</p><p>But how did they get in?</p><h2 id="chasing-red-herrings"><strong>Chasing Red Herrings</strong></h2><p>Claude Code’s first suspect: a low-version npm package called<code>device-unique-keygen</code>. Added by a developer whose email matched the package maintainer. Classic supply chain attack pattern.</p><p>I got excited. Maybe too excited.</p><p>Claude Code fetched the GitHub repo, analyzed the source code, checked for postinstall scripts, looked for obfuscated code, searched for eval() calls.</p><p>Nothing. The package was clean. Just a browser fingerprinting library. Boring. Legitimate.</p><p>We moved on.</p><p>No malicious init containers. No sidecars. No .ashrc shenanigans. The Dockerfile was clean. The pod spec was clean.</p><p>Everything was clean except someone was definitely mining crypto on our infrastructure.</p><h2 id="the-actual-answer"><strong>The Actual Answer</strong></h2><p>Claude Code ran<code>npm audit</code> on the codebase.</p><pre><code>critical │ Next.js is vulnerable to RCE in React flight protocol
Package │ next
Patched │ &gt;=15.3.6
Your ver │ 15.3.4
CVSS │ 10.0</code></pre><figure><a href="https://substackcdn.com/image/fetch/$s_!FrM8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":765,"width":1247,"resizeWidth":null,"bytes":128814,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/183314492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!FrM8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 424w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 848w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 1272w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 1456w" sizes="100vw" loading="lazy" width="1247" height="765"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p>CVSS 10. The maximum possible score. The “your house is actively on fire” of security ratings.</p><p>The app was running Next.js 15.3.4. A publicly disclosed RCE vulnerability. No authentication required. An attacker could run arbitrary commands on the server by sending a crafted request.</p><p>That’s exactly what happened. They sent a request, ran wget twice, downloaded the miner, and started extracting crypto value from compute cycles they weren’t paying for.</p><p>The container’s memory limit stopped them. A $20/month Kubernetes resource limit prevented what could have been ongoing theft.</p><h2 id="what-claude-code-actually-did"><strong>What Claude Code Actually Did</strong></h2><p>I want to be clear about what happened here. I didn’t single-handedly unravel a sophisticated attack. I didn’t manually correlate log timestamps or reverse-engineer obfuscated npm packages.</p><p>I said “check these logs” and Claude Code:</p><ul><li><p>Built a timeline from JSON log entries</p></li><li><p>Identified the malware artifacts and C2 server</p></li><li><p>Traced git blame to find who added suspicious packages</p></li><li><p>Fetched and analyzed source code from GitHub</p></li><li><p>Ruled out attack vectors one by one</p></li><li><p>Found the actual vulnerability via npm audit</p></li><li><p>Correlated the OOMKill timing with the attack</p></li><li><p>Suggested remediation and forensic preservation steps</p></li></ul><p>The entire investigation took under an hour. Not because I’m fast. Because Claude Code is.</p><h2 id="the-fix"><strong>The Fix</strong></h2><pre><code>pnpm update next@^15.3.6</code></pre><p>One command. That’s the remediation for a CVSS 10.0 vulnerability.</p><p>We also orphaned the compromised pods for forensic analysis, rotated secrets, and added proper security contexts to prevent future wget adventures.</p><figure><a href="https://substackcdn.com/image/fetch/$s_!bCvw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":772,"width":1344,"resizeWidth":null,"bytes":121268,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/183314492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!bCvw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 424w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 848w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 1272w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 1456w" sizes="100vw" loading="lazy" width="1344" height="772"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><h2 id="the-lesson"><strong>The Lesson</strong></h2><p>Two things saved us:</p><ol><li><p>Centralized logging (couldn’t investigate without the logs)</p></li><li><p>Memory limits (the attacker’s miner killed itself)</p></li></ol><p>One thing would have prevented this entirely: running<code>npm audit</code> before deployment.</p><p>The attacker exploited a vulnerability that was publicly disclosed and patched. We just hadn’t updated yet.</p><p>Godspeed with your own dependency updates.</p><hr><p>My Medium friends can read this<a href="https://medium.com/@lakshminp/i-found-a-cryptominer-in-my-clients-production-cluster-claude-code-found-the-attacker-ae6148ec0514" rel="external nofollow noopener" class="lnp-link">over there<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> as well.</p>
]]></content:encoded></item><item><title>I Spent Weeks Confused About Claude Code's 5 Concepts. Here's the Mental Model That Finally Clicked.</title><link>https://lakshminp.com/2025/12/claude-code-mental-model/</link><pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/claude-code-mental-model/</guid><category>essays</category><category>claude-code</category><description>Slash commands, skills, agents, MCP servers, plugins. Five concepts. Five different jobs. One very confused developer (me) trying to figure out which one to use when.
You&amp;rsquo;re using Claude Code. You&amp;rsquo;ve seen these terms thrown around like confetti at a developer conference. Slash commands! Skills! Agents! MCP servers! Plugins! Each one sounds important. Each one sounds slightly different from the others. Each one makes you wonder if you&amp;rsquo;re using Claude Code wrong.</description><content:encoded>&lt;![CDATA[<p>Slash commands, skills, agents, MCP servers, plugins. Five concepts. Five different jobs. One very confused developer (me) trying to figure out which one to use when.</p><p>You&rsquo;re using Claude Code. You&rsquo;ve seen these terms thrown around like confetti at a developer conference. Slash commands! Skills! Agents! MCP servers! Plugins! Each one sounds important. Each one sounds slightly different from the others. Each one makes you wonder if you&rsquo;re using Claude Code wrong.</p><p>Spoiler: you probably are. But so is everyone else, so don&rsquo;t feel too special about it.</p><p>Here&rsquo;s the mental model that finally made sense to me after weeks of confusion and several existential crises about whether I understood my own tools.</p><h2 id="the-one-sentence-version">The one-sentence version</h2><p><strong>Slash commands</strong> = shortcuts you trigger manually (like a civilized person)</p><p><strong>Skills</strong> = instructions Claude<em>might</em> trigger automatically (emphasis on &ldquo;might&rdquo;)</p><p><strong>Agents</strong> = autonomous workers with their own context (little worker bees you send off to do your bidding)</p><p><strong>MCP servers</strong> = external capabilities like browsers, databases, APIs (the things that let Claude actually<em>do</em> stuff in the real world)</p><p><strong>Plugins</strong> = packaging that bundles any combination of the above (a zip file for your AI workflow, basically)</p><p>That&rsquo;s it. Five concepts. Five different jobs. The confusion happens because they overlap like a Venn diagram designed by someone who hates clarity. A slash command can spawn an agent. An agent can use MCP servers. A plugin can contain all of the above. It&rsquo;s turtles all the way down.</p><p>(I&rsquo;m not covering<strong>hooks</strong> here — automations that fire on events like file saves. That&rsquo;s a whole other therapy session.)</p><h2 id="the-key-distinction-most-people-miss">The key distinction most people miss</h2><p>Here&rsquo;s what nobody tells you upfront:<strong>who decides when something runs?</strong></p><ul><li>Slash commands:<strong>You</strong> trigger them. Like pressing a button. Revolutionary concept.</li><li>Skills:<strong>Claude</strong> triggers them. In theory. When it feels like it. Maybe.</li><li>Agents:<strong>You</strong> spawn them, then they run autonomously until they&rsquo;re done or your tokens are.</li><li>MCP servers:<strong>Claude</strong> calls them when it needs to reach outside your codebase.</li><li>Plugins:<strong>You</strong> install them. They&rsquo;re just containers.</li></ul><p>This matters more than all the technical mumbo-jumbo. Want control? Slash commands. Want Claude to figure it out? Skills. Want to let something loose and hope for the best? Agents. Want Claude to actually interact with the real world? MCP servers.</p><h2 id="the-decision-tree-for-people-who-dont-want-to-think-about-this-anymore">The decision tree (for people who don&rsquo;t want to think about this anymore)</h2><p>When you&rsquo;re about to ask Claude to do something, run through this:</p><p><strong>Is it a repeatable task with fixed steps?</strong> → Slash command. Done. Move on with your life.</p><p><strong>Does it need to access external systems?</strong> → MCP server. Claude can&rsquo;t browse the web or query databases with pure thought. Yet.</p><p><strong>Does it require exploration and figuring things out?</strong> → Agent. Let it wander. It&rsquo;s smarter than you think. Sometimes.</p><p><strong>Is it domain-specific instructions that don&rsquo;t always apply?</strong> → Skill. Good luck getting Claude to actually use it without being asked.</p><p><strong>Do you want to share your setup with others?</strong> → Package it as a plugin. Make it someone else&rsquo;s problem.</p><h2 id="the-overlap-problem-or-why-everyone-builds-three-things-for-the-same-task">The overlap problem (or: why everyone builds three things for the same task)</h2><p>Here&rsquo;s what happens in the wild: developers build a slash command, a skill, AND an agent for the same task. I&rsquo;ve done it. You&rsquo;ve probably done it. We&rsquo;ve all sinned.</p><p>Pick one primary approach:</p><ul><li>Need<strong>control over when it runs</strong> → slash command</li><li>Need<strong>Claude to decide when it&rsquo;s relevant</strong> → skill (and a prayer)</li><li>Need<strong>autonomous multi-step execution</strong> → agent</li><li>Need<strong>external system access</strong> → MCP server</li><li>Need<strong>to share your setup</strong> → plugin</li></ul><p>Use the others to support, not duplicate. Your future self will thank you when you&rsquo;re not debugging three different implementations of the same thing at 2 AM.</p><h2 id="how-they-layer-the-actually-useful-part">How they layer (the actually useful part)</h2><p>Think of it as a stack:</p><ul><li><strong>Skills</strong> = instructions (how to do things)</li><li><strong>Slash commands</strong> = triggers (entry points you control)</li><li><strong>Agents</strong> = workers (autonomous task executors)</li><li><strong>MCP servers</strong> = capabilities (external system access)</li><li><strong>Plugins</strong> = packaging (bundles of all the above)</li></ul><p>A slash command can spawn an agent. An agent can use MCP servers. A skill can teach Claude how to use an MCP server efficiently. A plugin can package all of this into something you can share on GitHub and pretend makes you a thought leader.</p><p>They compose. They don&rsquo;t compete. Unless you make them compete, in which case, godspeed.</p><hr><p>This is post 1 of 4. Next up: slash commands vs skills — and why skills don&rsquo;t work the way the documentation promises they will.</p><p>I help technical founders develop, deploy, and market their SaaS using Claude Code. This is the kind of workflow clarity I wish someone had given me three months ago.</p>
]]></content:encoded></item><item><title>I spent years on Kubernetes. Now I'm betting against it.</title><link>https://lakshminp.com/2025/12/kubernetes-indie-dev-alternative/</link><pubDate>Thu, 04 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/kubernetes-indie-dev-alternative/</guid><category>essays</category><category>kubernetes</category><description>I’ve spent years in the Kubernetes ecosystem. I wrote about K3s. I ran production clusters. I know my way around kubectl, Helm charts, and the CNCF landscape.
And I’m building a deployment tool that doesn’t use any of it.
Here’s why.
Kubernetes solves problems you don’t have K8s is incredible engineering. It solves real problems:
Multi-team deployments without stepping on each other
Automatic failover across dozens of nodes
Fine-grained resource allocation at massive scale</description><content:encoded>&lt;![CDATA[<p>I’ve spent years in the Kubernetes ecosystem. I wrote about K3s. I ran production clusters. I know my way around kubectl, Helm charts, and the CNCF landscape.</p><p>And I’m building a deployment tool that doesn’t use any of it.</p><p>Here’s why.</p><h2 id="kubernetes-solves-problems-you-dont-have"><strong>Kubernetes solves problems you don’t have</strong></h2><p>K8s is incredible engineering. It solves real problems:</p><ul><li><p>Multi-team deployments without stepping on each other</p></li><li><p>Automatic failover across dozens of nodes</p></li><li><p>Fine-grained resource allocation at massive scale</p></li><li><p>Rolling updates for services with thousands of instances</p></li></ul><p>If you’re Spotify, you need this. If you’re running a 50-person engineering org, you need this.</p><p>If you’re a solo dev with one FastAPI app and a Celery worker? You don’t.</p><p>As one dev put it: “Do you want to build a product, or do you want to build an infrastructure team? Kubernetes makes sense for the latter, but it’s often overkill for the former.”</p><p>You need:</p><ul><li><p>git push → app is live</p></li><li><p>Rollback when you break something</p></li><li><p>Logs you can actually read</p></li><li><p>Alerts when the site goes down</p></li></ul><p>That’s it. Everything else is ceremony.</p><h2 id="the-hidden-cost-isnt-the-cluster"><strong>The hidden cost isn’t the cluster</strong></h2><p>“But K3s is lightweight! You can run it on a $6 VPS!”</p><p>True. I’ve done it. Here’s what they don’t tell you:</p><p>A solo dev<a href="https://www.reddit.com/r/kubernetes/comments/1p2k9xd/solo_dev_tired_of_k8s_churn_what_are_my_options/" rel="external nofollow noopener" class="lnp-link">recently posted<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> on r/kubernetes with a title that said it all: “Solo dev tired of K8s churn&hellip; What are my options?”</p><p>His pain point wasn’t learning Kubernetes. It was the maintenance:</p><blockquote><p>“I don’t mind learning the topics and writing the config, I do mind having to deal with a lot of work out of nowhere just because the underlying tools are beyond my control and requiring breaking updates.”</p></blockquote><p>He’d been burned by Bitnami charts pulling the rug, NGINX ingress breaking changes. Things that worked stopped working — not because he changed anything, but because the ecosystem did.</p><blockquote><p>“It all felt very straightforward, and it worked so well for a bit, but it starts to crumble even when I haven’t changed anything on my side.”</p></blockquote><p>This is the hidden cost. Not the setup — the churn.</p><p><strong>The YAML tax</strong>: Every change requires editing manifests. Add an env var? YAML. Change a port? YAML. Want a cron job? That’s a whole new CronJob resource. One team had a production outage caused by an improperly indented YAML line. A single space broke prod.</p><p><strong>The debugging tax</strong>: Something’s wrong. Is it the pod? The service? The ingress? The network policy? The PVC? Hope you remember how to read<code>kubectl describe</code>.</p><p><strong>The upgrade tax</strong>: K3s made this easier, but you’re still running a distributed system. A 2024 report found over 77% of Kubernetes practitioners still have issues running their clusters — up from 66% in 2022. It’s getting harder, not easier.</p><p><strong>The cognitive tax</strong>: Part of your brain is always allocated to “how does Kubernetes work” instead of “how do I ship features.”</p><p>As one commenter put it: “Choose your churn.” There’s always something.</p><p>The Reddit OP’s conclusion? He gave up on K8s entirely. Settled on plain NixOS on a single Hetzner VPS. Accepted that 99.9% uptime from one server is good enough. Skipped the redundancy he thought he needed.</p><blockquote><p>“I am trying to write my software, I just want a reliable thing to host it with the freedom and reliability that one would expect from a system that stays out of your way.”</p></blockquote><p>That’s the real ask. A system that stays out of your way.</p><p>For teams, the Kubernetes tax is worth paying. You split it across people, you build expertise, you amortize the cost.</p><p>Solo? You pay it all yourself, every time.</p><h2 id="what-actually-works-for-solo-devs"><strong>What actually works for solo devs</strong></h2><p>So if not Kubernetes, what?</p><p>The same Reddit OP nailed the PaaS problem too:</p><blockquote><p>“These ‘managed-docker’ services charge per container/pod and force the user to over-provision. Your pod doesn’t run on 250mb RAM? Ok pay for 1GB even though you only need 500mb.”</p></blockquote><p>I’ve tried everything:</p><ul><li><p>Heroku (great until the bill hits)</p></li><li><p>Railway/Render (same story, nicer UX — $50-100/mo for what costs $5 on a VPS)</p></li><li><p>Dokku (solid, but showing its age)</p></li><li><p>Coolify (powerful, but now you’re babysitting another server)</p></li><li><p>K3s (overkill for most solo projects)</p></li><li><p>Raw Docker + nginx (works but tedious)</p></li></ul><p>The best setup I’ve found:<strong>Kamal</strong>.</p><p>It’s from 37signals. They run Basecamp and HEY on it. It’s just Docker + SSH. No cluster, no orchestrator, no YAML manifests.</p><pre><code>kamal deploy</code></pre><p>That’s it. It SSHs into your server, pulls your container, does a zero-downtime swap. Rollback is one command. Logs are one command.</p><p>It’s boring. It works.</p><h2 id="my-bet-ai-interface--dashboards--cli--yaml"><strong>My bet: AI interface &gt; dashboards &gt; CLI &gt; YAML</strong></h2><p>Here’s where it gets interesting.</p><p>Kamal solved the “deploy” problem. But ops is more than deploy:</p><ul><li><p>Why is the app slow right now?</p></li><li><p>What happened at 3am?</p></li><li><p>Should I upgrade my VM or optimize my code?</p></li><li><p>Show me the errors from the last hour</p></li></ul><p>These questions require jumping between tools. SSH into the box, grep the logs, check Grafana, cross-reference with your deploy history.</p><p>My bet: you shouldn’t need to do any of that.</p><p>You should just ask.</p><p>“Why is memory usage spiking?” → Here’s what’s using RAM, and here’s the trend over the last week.</p><p>“Roll back to yesterday’s deploy” → Done. Here’s what changed.</p><p>“Show me errors from the /api/checkout endpoint” → Found 47 errors, here’s the pattern.</p><p>This isn’t science fiction. LLMs are good at this now. The interface just doesn’t exist yet.</p><h2 id="what-im-building"><strong>What I’m building</strong></h2><p>VMKit is my attempt at this interface.</p><ul><li><p>Bring your own VPS (Hetzner, DigitalOcean, whatever)</p></li><li><p>It handles Kamal, Traefik, SSL, monitoring</p></li><li><p>The interface is conversation — web chat or MCP server in Claude Code</p></li></ul><p>No Kubernetes. No YAML manifests. No 47-screen dashboards.</p><p>Just say what you want.</p><p>I might be wrong. Maybe solo devs actually love clicking through Render’s UI. Maybe the Kubernetes complexity is worth it for everyone.</p><p>But I don’t think so. I think the right answer for one person running one to three apps is radically simpler than what we have today.</p><p><a href="https://vmkit.dev/" rel="external nofollow noopener" class="lnp-link">vmkit.dev<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> if you want to follow along.</p><h2 id="the-uncomfortable-truth"><strong>The uncomfortable truth</strong></h2><p>I’m not anti-Kubernetes. I’m anti-complexity-for-its-own-sake.</p><p>K8s is a tool. An incredibly powerful one. But tools have contexts where they make sense and contexts where they don’t.</p><p>Solo dev shipping a SaaS? You don’t need pod autoscaling. You need deploys that work and a way to debug when they don’t.</p><p>That’s the bet.</p>
]]></content:encoded></item><item><title>Why Your AI Wakes Up Every Morning With No Memory (And how to fix it)</title><link>https://lakshminp.com/2025/11/ai-agent-memory-persistence/</link><pubDate>Tue, 11 Nov 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/11/ai-agent-memory-persistence/</guid><category>essays</category><category>claude-code</category><description>I was two weeks into a gnarly refactor when it happened.
Claude and I had been pair programming on an authentication system—tracking down race conditions, filing away “fix this later” issues, building up this rich context about why we made certain decisions. RS256 instead of HS256 for key rotation. Session middleware patterns. The whole architecture was in our shared understanding.
Then I hit compaction.
I came back the next day, opened a new Claude session, and asked: “Where did we leave off?”</description><content:encoded>&lt;![CDATA[<p>I was two weeks into a gnarly refactor when it happened.</p><p>Claude and I had been pair programming on an authentication system—tracking down race conditions, filing away “fix this later” issues, building up this rich context about why we made certain decisions. RS256 instead of HS256 for key rotation. Session middleware patterns. The whole architecture was in our shared understanding.</p><p>Then I hit compaction.</p><p>I came back the next day, opened a new Claude session, and asked: “Where did we leave off?”</p><p>Claude: “I don’t have information about previous sessions in my context.”</p><p><strong>All of it. Gone.</strong></p><p>The discovered bugs. The architectural decisions. The “by the way, we should fix this” notes. Everything we’d built up over dozens of hours—evaporated.</p><p>I spent 30 minutes re-explaining what we’d been working on. And even then, I couldn’t remember all the issues Claude had surfaced. How many edge cases had we found? Which ones were critical? What was blocking what?</p><p>This is what I call the<strong>amnesia problem</strong>. And it’s not just annoying—it’s a fundamental limitation of how we work with AI agents.</p><h2 id="the"><strong>The<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> Trap</strong></h2><p>So I did what everyone does: I created a<code>TODO.md</code> file.</p><pre><code>## TODO
- [ ] Add rate limiting to login endpoint
- [ ] Improve password hashing
- [ ] Fix email validation
- [ ] Build dashboard (depends on auth being done)</code></pre><p>Seemed reasonable. Every project has a TODO list, right?</p><p><strong>Three days later, it was already a graveyard.</strong></p><p>Half the items were done but still unchecked. A quarter were outdated. New issues Claude discovered during implementation? Lost in chat history. Dependencies? I had “(depends on auth being done)” in a parenthetical. Good luck having Claude parse that reliably after compaction.</p><p>Steve Yegge calls these “swamps of rotten half-implemented plans.” He’s right.</p><p>Here’s why markdown TODOs fail with AI agents:</p><p><strong>They become stale instantly</strong> - You finish a task, forget to update the markdown. The agent reads it, doesn’t know what’s actually done.</p><p><strong>No dependency tracking</strong> - Can I start the dashboard? Is auth done? The agent has to guess.</p><p><strong>Context evaporates</strong> - “Fix email validation” tells you nothing. Which email? Where? What’s broken? Why does it matter? After compaction, this line is worthless.</p><p><strong>Agents can’t use them reliably</strong> - Claude reads the whole list, can’t tell what’s ready to work on, and often just&hellip; ignores it.</p><p>That<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a> file? After compaction, it’s all you have. And it’s not enough.</p><h2 id="enter-beads-a-memory-system-built-for-agents"><strong>Enter Beads: A Memory System Built for Agents</strong></h2><p>That’s when I found<a href="https://github.com/steveyegge/beads" rel="external nofollow noopener" class="lnp-link">beads<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</p><p>Steve Yegge built it specifically to solve the amnesia problem. It’s an issue tracker, but not like Jira or Linear. It’s<strong>built for AI agents, not humans.</strong></p><p>Here’s the breakthrough:<strong>You don’t manage beads. Claude does.</strong></p><p>I ran<code>bd init</code> in my project. That’s it. One command.</p><p>Then I just&hellip; kept working with Claude. And something wild happened.</p><p>Claude started filing issues automatically. Not when I asked—just when it discovered something worth remembering. While implementing the auth system, Claude would say:</p><p>“I notice there’s a potential race condition in the session handler. Filing it:</p><p><code>bd create ‘Fix race condition in user session’ --deps discovered-from:myapp-a4f2 -p 0</code>“</p><p>It wasn’t asking permission. It wasn’t waiting for me to maintain a TODO list.<strong>It was building its own memory.</strong></p><p>At the end of each session, Claude would update the notes on active issues:</p><pre><code>bd update myapp-a4f2 --notes “COMPLETED: JWT login with RS256
KEY DECISION: Chose RS256 over HS256 for key rotation capability
IN PROGRESS: Session middleware
BLOCKERS: Need decision on token expiry (1hr vs 24hr trade-off)
NEXT: Implement refresh endpoint after expiry decision”</code></pre><p>This wasn’t for me. This was<strong>for future-Claude.</strong></p><h2 id="the-compaction-survival-test"><strong>The Compaction survival Test</strong></h2><p>The next day, I hit compaction again. New session, empty context.</p><p>Me: “What should we work on?”</p><p>Claude (without me asking, automatically ran these commands):</p><pre><code>bd ready --json
bd list --status in_progress --json
bd show myapp-a4f2</code></pre><p>Then replied:</p><p>“I can see we’re working on authentication (<code>myapp-a4f2</code>). Last session we completed the JWT login flow, decided to use RS256 for key rotation capability, and got blocked waiting for your decision on token expiry time.</p><p>The trade-off is: 1-hour tokens (better security, worse UX) vs 24-hour tokens (better UX, but wider attack window if tokens are compromised). What’s your preference?</p><p>Also, I’ve filed 3 issues we discovered along the way:</p><ul><li><p><strong>Rate limiting for login endpoint</strong> (P0)</p></li><li><p><strong>Bcrypt rounds for password hashing</strong> (P1)</p></li><li><p><strong>Race condition in session handler</strong> (P0)</p></li></ul><p>Should we resolve the token expiry question first, or tackle one of the P0 issues?”</p><p><strong>The conversation history was gone. But Claude had full context.</strong></p><p>Every decision. Every discovered bug. Every blocker. Every trade-off. Right there.</p><p>No re-explaining. No “wait, what were we doing?” No hunting through old conversations.</p><p>This is what beads does.</p><h2 id="todowrite-vs-beads-two-memory-systems"><strong>TodoWrite vs Beads: Two Memory Systems</strong></h2><p>Here’s where people get confused. Claude actually has<em>two</em> memory systems, and they serve different purposes.</p><h3 id="todowrite-working-memory-this-hour"><strong>TodoWrite: Working Memory (This Hour)</strong></h3><p>TodoWrite is Claude’s<strong>scratch pad for the current session</strong>:</p><pre><code>✓ [completed] Implement login endpoint
→ [in_progress] Add password hashing
[pending] Create session middleware</code></pre><p>It shows you real-time progress. Gets marked complete as work happens.<strong>Disappears when the session ends.</strong></p><p>Perfect for: “What’s Claude doing right now?”</p><h3 id="beads-long-term-memory-this-weekmonth"><strong>Beads: Long-Term Memory (This Week/Month)</strong></h3><p>Beads is Claude’s<strong>episodic memory across sessions</strong>:</p><pre><code>bd show myapp-a4f2
Notes: “COMPLETED: Login with bcrypt (12 rounds)
KEY DECISION: JWT (not sessions) for stateless auth
IN PROGRESS: Session middleware
NEXT: Need input on token expiry (1hr vs 24hr)”</code></pre><p>Survives compaction. Captures meaning, not just tasks.<strong>Persists across all sessions.</strong></p><p>Perfect for: “What happened last week? What decisions were made?”</p><h3 id="the-handoff-pattern"><strong>The Handoff Pattern</strong></h3><ol><li><p><strong>Session start</strong>: Claude reads bead notes → creates TodoWrite items for immediate work</p></li><li><p><strong>During work</strong>: TodoWrite gets marked complete</p></li><li><p><strong>Reach milestone</strong>: Claude updates bead notes with outcomes + context</p></li><li><p><strong>Session end</strong>: TodoWrite disappears, bead survives with enriched notes</p></li></ol><p><strong>After compaction</strong>: TodoWrite is gone forever. Bead notes reconstruct everything.</p><h2 id="the-magic-dependencies-that-prevent-mistakes"><strong>The Magic: Dependencies That Prevent Mistakes</strong></h2><p>This is where beads gets brilliant. It supports four relationship types:</p><h3 id="1-blocks---hard-blocker"><strong>1.</strong><code>blocks</code><strong>- Hard Blocker</strong></h3><pre><code>bd create “Build user dashboard” -p 1
# Created myapp-e3f7
bd create “Implement authentication” -p 0
# Created myapp-g2h9
bd dep add myapp-e3f7 myapp-g2h9
# → “myapp-g2h9 blocks myapp-e3f7”</code></pre><p>Now dashboard won’t show in<code>bd ready</code> until auth is closed. Claude<strong>can’t accidentally start building the dashboard before auth exists.</strong></p><h3 id="2-discovered-from---the-audit-trail"><strong>2.</strong><code>discovered-from</code><strong>- The Audit Trail</strong></h3><p>This is the agent’s secret weapon:</p><pre><code># Claude finds bug B while implementing feature A
bd create “Fix memory leak in session handler” \
--deps discovered-from:myapp-a4f2 -p 0</code></pre><p>Creates an audit trail of how work was found. Those “oh by the way” issues Claude mentions? They now get filed permanently, linked to context.</p><p>After a week of work, you have an<strong>automatically maintained discovery backlog</strong>. Prioritized. Linked. Ready to tackle.</p><h3 id="3-parent-child---hierarchy"><strong>3.</strong><code>parent-child</code><strong>- Hierarchy</strong></h3><pre><code>bd create “Epic: Authentication system” -t epic
# Created myapp-j4k2
bd create “Add OAuth” --parent myapp-j4k2
# Created myapp-l8m1 (auto-linked)</code></pre><p>Good for breaking down large features.</p><h3 id="4-related---soft-connection"><strong>4.</strong><code>related</code><strong>- Soft Connection</strong></h3><pre><code>bd dep add myapp-b7c3 myapp-d1e8 -t related
# “These touch the same code but don’t block each other”</code></pre><h2 id="what-you-actually-do-almost-nothing"><strong>What You Actually Do (Almost Nothing)</strong></h2><p><strong>Your workflow:</strong></p><p><strong>One-time setup:</strong></p><pre><code>cd your-project
bd init</code></pre><p>Done. That’s it.</p><p><strong>Work with Claude normally:</strong></p><p>“Let’s build user authentication”</p><p>Claude automatically:</p><ul><li><p>Creates issues as work emerges</p></li><li><p>Tracks dependencies</p></li><li><p>Updates notes at milestones</p></li><li><p>Files discovered work with proper links</p></li><li><p>Checks ready work at session start</p></li></ul><p><strong>You just work.</strong> The memory management happens in the background.</p><p><strong>When you DO interact with beads</strong> (rarely):</p><pre><code># Weekly review
bd stats
# Check what’s blocked
bd blocked
# Context restore after time away
bd show myapp-a4f2</code></pre><p>The agent does the rest.</p><h2 id="why-claude-loves-it"><strong>Why Claude Loves It</strong></h2><p>The most interesting thing about beads isn’t the technology. It’s<strong>how Claude uses it.</strong></p><p>Claude’s behavior changes:</p><p><strong>1. Proactive filing</strong>: Claude files issues without being asked. “I notice X could be improved. Filing:<code>bd create...</code>“</p><p><strong>2. Better planning</strong>: Claude uses dependencies to think through work order before starting.</p><p><strong>3. Context awareness</strong>: Claude references past decisions from bead notes. “Last session we decided to use RS256 because&hellip;”</p><p><strong>4. Discovery tracking</strong>: Claude treats discovered work as first-class, not throwaways.</p><p><strong>Why?</strong> Because beads is built for how Claude actually works:</p><ul><li><p>Structured data (JSON)</p></li><li><p>Clear state (open/in_progress/closed)</p></li><li><p>Explicit relationships (dependencies)</p></li><li><p>Queryable memory (show me what’s ready)</p></li></ul><p>It’s not forcing Claude into a human workflow. It’s giving Claude the database it naturally wants.</p><h2 id="when-beads-is-overkill"><strong>When Beads is Overkill</strong></h2><p>Not every task needs beads. Use this test:</p><h3 id="use-beads-when"><strong>Use Beads when:</strong></h3><ul><li><p>Work spans multiple sessions</p></li><li><p>You might hit compaction before finishing</p></li><li><p>There are dependencies or blockers</p></li><li><p>You’re discovering related work along the way</p></li><li><p>You need to resume after time away</p></li></ul><p><strong>Example</strong>: “Build authentication system” (multi-day, many parts)</p><h3 id="use-todowrite-when"><strong>Use TodoWrite when:</strong></h3><ul><li><p>Work completes in this session</p></li><li><p>It’s a simple linear checklist</p></li><li><p>All context is in the conversation</p></li><li><p>No dependencies or discovery</p></li></ul><p><strong>Example</strong>: “Refactor this 200-line file” (done in an hour)</p><p><strong>The test</strong>: “Will I need this context in 2 weeks?”</p><ul><li><p><strong>Yes</strong> → Beads</p></li><li><p><strong>No</strong> → TodoWrite</p></li></ul><h2 id="the-git-sync-how-it-works-across-machines"><strong>The Git Sync: How It Works Across Machines</strong></h2><p>Beads stores everything in two places:</p><ol><li><p><code>.beads/beads.db</code> - Local SQLite (fast queries)</p></li><li><p><code>.beads/issues.jsonl</code> - Git-versioned JSONL (syncs across machines)</p></li></ol><p><strong>On your desktop:</strong></p><pre><code>bd create “New issue”
# → SQLite write (instant)
# → After 5 seconds, exports to JSONL
# → Git commit with your code changes</code></pre><p><strong>On your laptop:</strong></p><pre><code>git pull
# → JSONL updates
# → bd auto-imports (newer than local DB)
# → SQLite now has the issue</code></pre><p>You get:</p><ul><li><p>Fast local operations (SQLite, &lt;100ms)</p></li><li><p>Git versioning (full audit trail)</p></li><li><p>Multi-machine sync (JSONL)</p></li><li><p>Offline support (no server)</p></li></ul><p>It’s a distributed database&hellip; that’s just files in git.</p><h2 id="memory-as-infrastructure"><strong>Memory as Infrastructure</strong></h2><p>We’re at this weird moment where AI coding agents are incredibly capable but also incredibly forgetful.</p><p>We expect them to remember complex multi-week projects, track dozens of discovered issues, maintain perfect context across compaction—but we give them&hellip; markdown files.</p><p><strong>Beads doesn’t make agents smarter. It makes them less forgetful.</strong></p><p>And honestly? That might be more important.</p><p>Because the hardest part of any project isn’t writing code. It’s<strong>not losing track of what needs to be written.</strong></p><p>Beads gives your agent:</p><ul><li><p>Memory that survives compaction</p></li><li><p>A discovery backlog that doesn’t evaporate</p></li><li><p>A dependency graph that prevents mistakes</p></li></ul><p><strong>And you barely have to do anything.</strong> Install it, initialize it, let Claude manage it.</p><p>The agent handles the rest.</p><p><strong>Get started:</strong></p><ul><li><p>GitHub:<a href="https://github.com/steveyegge/beads" rel="external nofollow noopener" class="lnp-link">steveyegge/beads<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a></p></li><li><p>Quick start:<code>bd init</code> in your project</p></li><li><p>Let Claude do the rest</p></li></ul><p><strong>Key commands</strong> (mostly for reference—Claude uses these automatically):</p><pre><code>bd init # One-time setup
bd ready # What’s ready? (Claude checks this)
bd show &lt;id&gt; # Issue details (Claude reads notes)
bd stats # Weekly review (you use this)
bd blocked # What’s stuck?</code></pre><p>Give your agent a memory. See what happens.</p>
]]></content:encoded></item><item><title>I Watched AI Generate a Perfect Todo App in 3 Minutes. Then I Spent 3 Days Fixing It.</title><link>https://lakshminp.com/2025/11/ai-todo-app-3-minutes-3-days/</link><pubDate>Fri, 07 Nov 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/11/ai-todo-app-3-minutes-3-days/</guid><category>essays</category><category>ai-coding</category><description>Every AI coding tool demo starts the same way.
“Build me a todo app.”
Four words. Maybe ten seconds of typing. Then you sit back and watch the magic: files appear, databases materialize, endpoints generate themselves. The AI spins up authentication, adds a sleek frontend, writes tests. Three minutes later, you have a working application.
It’s impressive. It’s seductive. And for production software you’ll maintain for years, it’s a starting point at best—not a solution.</description><content:encoded>&lt;![CDATA[<p>Every AI coding tool demo starts the same way.</p><p>“Build me a todo app.”</p><p>Four words. Maybe ten seconds of typing. Then you sit back and watch the magic: files appear, databases materialize, endpoints generate themselves. The AI spins up authentication, adds a sleek frontend, writes tests. Three minutes later, you have a working application.</p><p>It’s impressive. It’s seductive. And for production software you’ll maintain for years, it’s a starting point at best—not a solution.</p><h2 id="the-demo-that-sells-vs-the-code-you-ship"><strong>The Demo That Sells vs. The Code You Ship</strong></h2><p>I’ve spent five months deep in AI coding tools—Claude Code, claude-flow, and everything in between. I’ve watched hundreds of demos. I’ve read the marketing. And I’ve built actual production SaaS applications.</p><p>Here’s what the demos won’t tell you: that three-minute todo app works because it makes a thousand architectural decisions you never specified. And the moment your requirements diverge from those invisible assumptions, the whole thing falls apart.</p><p>Let me show you what I mean.</p><h2 id="the-eight-decisions-that-actually-matter"><strong>The Eight Decisions That Actually Matter</strong></h2><p>When you say “build me a todo app,” you think you’re giving clear instructions. But try building real production software and you’ll immediately hit these questions:</p><p><strong>1. JWT Claims Structure</strong></p><ul><li><p>What exact fields go in your JWT payload?</p></li><li><p>Do you store roles as an array or a single string?</p></li><li><p>Where do permissions live? In the token? In the database?</p></li><li><p>Do you include user metadata or just an ID?</p></li></ul><p>The demo picks one. It might not be the one you need. And changing it later? That’s not a refactor. That’s rearchitecting your entire auth system.</p><p><strong>2. Token Rotation</strong></p><ul><li><p>15-minute access tokens with 7-day refresh tokens?</p></li><li><p>Refresh token rotation on every use?</p></li><li><p>Where do you store refresh tokens—database, Redis, or in-memory?</p></li><li><p>httpOnly cookies or localStorage?</p></li></ul><p>The demo makes a choice. You won’t know what it chose until you’re debugging your third session timeout bug in production.</p><p><strong>3. UI Library</strong></p><ul><li><p>shadcn/ui? Material-UI? Chakra? Ant Design? Headless UI?</p></li><li><p>Tailwind CSS or CSS-in-JS?</p></li><li><p>Which component patterns?</p></li></ul><p>“Use a modern UI library” means nothing. I needed shadcn/ui specifically because it works with my design system, ships minimal JavaScript, and uses Tailwind. The demo gave me Material-UI. That’s not a theme change—that’s rebuilding the entire frontend.</p><p><strong>4. Stripe Integration</strong></p><ul><li><p>Checkout flow or Payment Intents?</p></li><li><p>Subscription model or one-time payments?</p></li><li><p>Customer portal or custom UI?</p></li><li><p>Which webhooks do you handle?</p></li></ul><p>The difference isn’t cosmetic. Checkout and Payment Intents are architecturally different. Choosing wrong means rewriting your entire billing integration.</p><p><strong>5. Email Provider</strong></p><ul><li><p>SendGrid? Resend? Postmark? AWS SES?</p></li><li><p>Template system?</p></li><li><p>Transactional vs. marketing?</p></li></ul><p>Each provider has different APIs, rate limits, pricing models, and deliverability characteristics. “Add email notifications” doesn’t specify any of this.</p><p><strong>6. Database ORM</strong></p><ul><li><p>Prisma? Drizzle? TypeORM? Kysely?</p></li><li><p>Type generation approach?</p></li><li><p>Migration strategy?</p></li></ul><p>Your ORM choice affects type safety, migration workflows, query performance, and deployment strategy. It’s not swappable. It’s foundational.</p><p><strong>7. Testing Framework</strong></p><ul><li><p>Vitest? Jest? Mocha?</p></li><li><p>Supertest for integration tests?</p></li><li><p>What coverage target?</p></li></ul><p>The testing framework dictates how you structure tests, handle mocks, and integrate with CI/CD. Changing it later means rewriting every test.</p><p><strong>8. Deployment Target</strong></p><ul><li><p>Vercel? AWS? Docker compose? Railway?</p></li><li><p>What Vercel-specific features do you need?</p></li><li><p>Environment variable strategy?</p></li><li><p>Database hosting (Neon? Supabase? RDS?)?</p></li></ul><p>Deployment isn’t the last step. It shapes your entire architecture—serverless vs. long-running, filesystem access, background jobs, caching strategies.</p><h2 id="the-just-refactor-it-myth"><strong>The “Just Refactor It” Myth</strong></h2><p>When I point this out, the response is always: “Just refactor what the AI generated.”</p><p>Have you actually tried this?</p><p>Swapping Prisma for Drizzle isn’t a find-and-replace operation. It means:</p><ul><li><p>Rewriting your schema in a different DSL</p></li><li><p>Changing how you handle migrations</p></li><li><p>Updating every database query</p></li><li><p>Modifying your type generation</p></li><li><p>Adjusting your seeding scripts</p></li><li><p>Updating your testing setup</p></li></ul><p>We’re not talking about an afternoon. We’re talking about days of work. And that’s for ONE of these eight decisions.</p><p>Change the ORM, the UI library, and the auth token structure? You’re not refactoring. You’re rebuilding.</p><h2 id="what-build-me-an-app-actually-produces"><strong>What “Build Me an App” Actually Produces</strong></h2><p>Here’s the brutal truth: autonomous AI tools generate generic boilerplate that matches their training data’s most common patterns.</p><p>They give you:</p><ul><li><p>Whatever stack is most popular on GitHub</p></li><li><p>Whatever patterns appear most in tutorials</p></li><li><p>Whatever architecture is easiest to generate</p></li></ul><p>They don’t give you:</p><ul><li><p>Your company’s conventions</p></li><li><p>Your infrastructure constraints</p></li><li><p>Your team’s expertise</p></li><li><p>Your product’s specific requirements</p></li></ul><p>The demo works because demos don’t have requirements. Real projects die in the gap between “an app” and “our app.”</p><h2 id="why-this-matters-for-production-code"><strong>Why This Matters for Production Code</strong></h2><p>If you’re at a big company with a team of 20 engineers, maybe you can absorb the rebuild cost. You have engineering hours to burn. You have people to maintain legacy code while others refactor.</p><p>Most of us don’t have that luxury.</p><p>Whether you’re building solo, on a small team, or shipping client work, you’re living with every architectural decision for years. You can’t afford to spend three days ripping out Material-UI because an autonomous tool decided that’s what “modern UI library” meant. You can’t rebuild your auth system because the JWT structure doesn’t match your API contracts. You can’t rewrite billing integration because the tool guessed Checkout when you needed Payment Intents.</p><p>Wrong architectural decisions compound. When you’re responsible for maintaining the code—whether that’s yourself, a small team, or a client relationship—you need to understand and own those decisions.</p><p>That’s why production code requires control, not autonomy.</p><h2 id="the-interactive-alternative"><strong>The Interactive Alternative</strong></h2><p>Compare that to working with Claude Code:</p><p><strong>Me:</strong> “Add authentication to this project.”</p><p><strong>Claude Code:</strong> “I can help with that. A few questions:</p><ul><li><p>JWT or session-based auth?</p></li><li><p>If JWT, what should the token payload include?</p></li><li><p>Where should refresh tokens be stored?</p></li><li><p>What’s your refresh token rotation strategy?”</p></li></ul><p><strong>Me:</strong> “JWT. Payload should have userId, email, roles as an array, and permissions as a nested object. Refresh tokens in database with rotation on every use. 15-minute access, 7-day refresh. httpOnly cookies.”</p><p><strong>Claude Code:</strong> “Got it. I’ll implement that exactly.”</p><p>The specification happened through dialogue. I clarified the architectural decisions before any code was written. The AI generated exactly what I specified, not what it guessed I might want.</p><p>When the auth system is running in production six months later and I need to debug a token issue, I understand every decision because I made every decision. I’m not reverse-engineering someone else’s assumptions. I’m working with my own architecture.</p><h2 id="when-autonomy-actually-works"><strong>When Autonomy Actually Works</strong></h2><p>Autonomy isn’t wrong—it’s just context-dependent. There are places where “just handle it” is absolutely the right answer:</p><ul><li><p><strong>README generation:</strong> Standard markdown structure is fine</p></li><li><p><strong>ESLint configuration:</strong> Default configs work for most cases</p></li><li><p><strong>.gitignore files:</strong> Use the templates</p></li><li><p><strong>Boilerplate CRUD endpoints:</strong> If they follow established patterns exactly</p></li><li><p><strong>Prototypes you’ll throw away:</strong> Exploration where decisions don’t matter yet</p></li></ul><p>These are low-stakes decisions with high standardization. Getting them “wrong” doesn’t cascade. You can change them later without rebuilding your application. Single-prompt generation shines here.</p><p>But authentication? Database schema? Tech stack? These are high-stakes, foundational decisions with cascading effects. This is where precision matters and guesswork fails.</p><h2 id="the-autonomy-illusion"><strong>The Autonomy Illusion</strong></h2><p>Here’s what the AI tool marketing doesn’t tell you:</p><p>More agents doesn’t mean better code. It means less control.</p><p>Sophisticated orchestration doesn’t mean better results. It means more complexity hiding the same specification problem.</p><p>“Just describe what you want” doesn’t work when architectural decisions require precision that natural language can’t provide.</p><p>I tested claude-flow—a sophisticated multi-agent system with 10+ agent templates, health monitoring, auto-scaling, 3-tier memory, and 60+ task types. Impressive infrastructure. But it still runs on string-based specifications. When I asked for shadcn/ui, there was no type safety, no validation, no guarantee the agent would interpret “shadcn/ui” as “shadcn/ui and absolutely nothing else.”</p><p>The specification layer is still natural language. And natural language is ambiguous.</p><h2 id="the-real-question"><strong>The Real Question</strong></h2><p>The question isn’t “Can AI build an app from a single prompt?”</p><p>The answer to that is yes. Absolutely. The demos prove it.</p><p>The real question is: “Can AI build YOUR app—with YOUR architecture, YOUR conventions, YOUR constraints—from a single prompt?”</p><p>The answer to that is no.</p><p>Not because the AI isn’t capable of generating code. It’s excellent at that.</p><p>But because “build me an app” leaves a thousand architectural decisions unspecified. And every one of those decisions matters when you’re shipping production software you’ll maintain for years.</p><h2 id="what-works-instead"><strong>What Works Instead</strong></h2><p>After five months of research, building real projects, and testing multiple tools, here’s what actually works:</p><p><strong>Start with control:</strong></p><ul><li><p>Make architectural decisions consciously</p></li><li><p>Specify tech stack, libraries, patterns explicitly</p></li><li><p>Use interactive tools that let you clarify requirements</p></li><li><p>Review and understand what’s being generated</p></li></ul><p><strong>Move to autonomy for execution:</strong></p><ul><li><p>Once patterns are established, autonomous tools can replicate them</p></li><li><p>Use autonomy for boilerplate that follows decided patterns</p></li><li><p>Let AI handle repetition, not decision-making</p></li></ul><p><strong>Return to control for integration:</strong></p><ul><li><p>Debugging requires understanding</p></li><li><p>Maintenance requires ownership</p></li><li><p>Evolution requires knowing why decisions were made</p></li></ul><p>The cycle is: design with control, execute with autonomy, integrate with control.</p><p>Not: autonomous generation followed by days of “just refactor it.”</p><h2 id="the-real-power-of-ai-coding"><strong>The Real Power of AI Coding</strong></h2><p>The promise of AI coding tools isn’t “describe an app in four words and get perfect code.”</p><p>The promise is: “Make architectural decisions at the speed of thought, then have those decisions implemented flawlessly.”</p><p>Interactive AI tools let you think at the architecture level while the AI handles the implementation level. You make decisions. The AI writes code. You maintain control and understanding. The AI handles the tedious translation from intent to syntax.</p><p>That’s the real 10x improvement.</p><p>Not “build me an app” magic that produces generic boilerplate you’ll spend days rebuilding.</p><p>But the ability to say “JWT with these exact claims, refresh rotation with this lifecycle, stored in httpOnly cookies” and get exactly that. First try. No guessing. No rebuilding.</p><h2 id="the-bottom-line"><strong>The Bottom Line</strong></h2><p>If you’re building serious software—production SaaS, client projects, anything you’ll maintain beyond next week—you need to understand what you’re building.</p><p>Autonomous tools that guess at your architecture don’t save time if you spend days fixing wrong assumptions.</p><p>Code you don’t understand becomes a liability the moment something breaks.</p><p>Decisions you never made can’t evolve with your requirements.</p><p>Control isn’t about micromanaging the AI. It’s about owning the architecture of software you’re responsible for maintaining.</p><p>The demos are impressive. The marketing is seductive. The promise of “just describe it” is tempting—and genuinely useful for the right contexts.</p><p>But for production software with real requirements, real constraints, and real consequences? Interactive tools that let you specify precisely what you need will outperform autonomous guesswork every time.</p><hr><p><strong>Building production SaaS as a solo technical founder?</strong> I write about AI tools, architectural decisions, and shipping solo. Subscribe to get the next essay.</p><p>Be skeptical of demos. Demand control. Ship code you understand.</p>
]]></content:encoded></item><item><title>The Junior Dev Paradox: We’re Speed-Running Past the Tutorial</title><link>https://lakshminp.com/2025/11/junior-dev-ai-paradox/</link><pubDate>Sat, 01 Nov 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/11/junior-dev-ai-paradox/</guid><category>essays</category><category>craft</category><description>So here’s a fun thought experiment: What happens when an entire generation of developers learns to code by never actually learning to code?
I don’t mean that in the gatekeepy “back in my day we walked uphill both ways in assembly language” sense. I mean it literally. Right now, today, someone is getting their first junior dev job having built an impressive portfolio of projects they couldn’t debug if their life depended on it.</description><content:encoded>&lt;![CDATA[<p>So here’s a fun thought experiment: What happens when an entire generation of developers learns to code by never actually learning to code?</p><p>I don’t mean that in the gatekeepy “back in my day we walked uphill both ways in assembly language” sense. I mean it literally. Right now, today, someone is getting their first junior dev job having built an impressive portfolio of projects they couldn’t debug if their life depended on it.</p><p>And honestly? I’m not sure if that’s a problem or just&hellip; different.</p><h2 id="the-thing-nobody-wants-to-say-out-loud">The Thing Nobody Wants to Say Out Loud</h2><p>We—the developers who learned pre-AI—spent an ungodly amount of time doing things that, in retrospect, might have been pointless. Memorizing syntax. Reading documentation cover to cover because Stack Overflow didn’t have the answer. Spending three hours debugging only to find a missing semicolon. Writing the same boilerplate for the thousandth time because that’s just how you learned patterns.</p><p>That grind built something, though. Call it intuition. Call it muscle memory. Call it the ability to look at a stack trace and just<em>know</em> where the problem is because you’ve seen that exact error forty times before. We developed pattern recognition through sheer repetitive exposure, like some kind of coding Stockholm syndrome.</p><p>Junior devs today can skip all of that. They can describe what they want and watch Claude or Copilot generate it. They can ship features on day one that would’ve taken us weeks to build as juniors. They can contribute to complex codebases without understanding half of what’s happening under the hood.</p><p>Which is either the most amazing democratization of technical skills in history, or we’re a generation of developers who are one AI outage away from complete helplessness.</p><p>Probably both.</p><h2 id="what-we-might-be-losing">What We Might Be Losing</h2><p>Here’s what I wonder about:</p><p>Can you develop debugging intuition if AI catches most of your bugs?</p><p>Can you build system design sense if you’ve never had to architect something from scratch?</p><p>Can you really understand<em>why</em> something works if you’ve only ever described<em>what</em> you want it to do?</p><p>The old way of learning had a built-in forcing function. You<em>had</em> to understand data structures because you couldn’t implement anything without them. You<em>had</em> to read error messages carefully because that was your only clue. You<em>had</em> to develop mental models of how systems work because there was no AI to abstract it away.</p><p>It was inefficient as hell. It was also weirdly effective.</p><p>Now we’ve got junior devs who can ship impressive features but might struggle to explain what a hash table is or why their O(n^2) solution is melting production. They know how to make things work; they just don’t always know<em>why</em> they work or<em>how</em> to fix them when they don’t.</p><p>And before someone shows up in the comments with “well actually, they can just ask AI to debug it”—sure, until they can’t. Until the AI doesn’t understand the problem. Until the codebase is too complex or too weird or too legacy. Until, I don’t know,<a href="https://www.lakshminp.com/p/when-claude-code-goes-down-a-meditation" rel="external nofollow noopener" class="lnp-link">Claude Code goes down for five hours and suddenly you’re naked without your safety net<span class="lnp-link-ext" aria-hidden="true"> ↗</span></a>.</p><h2 id="what-we-might-be-gaining">What We Might Be Gaining</h2><p>But here’s the flip side: maybe we’re romanticizing the struggle.</p><p>Junior devs today are learning different skills. They’re getting good at prompt engineering, at articulating problems clearly, at evaluating AI-generated solutions. They’re exposed to more patterns, more codebases, more architectural approaches in their first year than we saw in five.</p><p>They’re also spending less time on tedious nonsense. Nobody needs to memorize the exact syntax for array methods or spend a week setting up a development environment. That time gets redirected to actually building things, to experimenting, to shipping.</p><p>And maybe—<em>maybe</em>—the fundamentals that matter are changing. Maybe understanding how to architect a system is more valuable than knowing how to implement every piece of it. Maybe code review skills and the ability to verify solutions matter more than the ability to generate them from scratch.</p><p>Maybe the fact that they can be productive on day one is a feature, not a bug.</p><h2 id="the-real-problem-the-copy-paste-generation">The Real Problem: The Copy-Paste Generation</h2><p>The actual risk isn’t that junior devs are using AI. It’s that some of them are using it as a crutch instead of a catalyst.</p><p>There’s a difference between “I don’t understand this, let me ask AI to explain it” and “I don’t understand this, so I’ll just copy-paste whatever AI gives me and hope it works.” One is learning accelerated by AI. The other is&hellip; well, it’s not learning at all.</p><p>We’re going to end up with a split: junior devs who use AI to move faster while still building understanding, and junior devs who are entirely dependent on AI to function. The<strong>first group will be terrifyingly productive</strong>. The second group is going to hit a wall the moment they encounter a problem AI can’t solve.</p><p>And here’s the uncomfortable part: it’s getting harder to tell them apart during hiring. Both can build impressive portfolios. Both can ship features. The difference only shows up when things break, when requirements get weird, when they need to dig into a gnarly legacy codebase that AI doesn’t understand.</p><h2 id="some-half-baked-solutions">Some Half-Baked Solutions</h2><p>So what do we do about this? I don’t have perfect answers, but here are some thoughts:</p><p><strong>For junior devs:</strong> Choose the harder path sometimes. Deliberately code without AI for practice. Build a project from scratch where you have to figure everything out manually. Read source code, not just documentation. When AI generates something, understand<em>why</em> it works before moving on. Treat AI as a tutor who’s always available, not a replacement for thinking.</p><p><strong>For seniors and mentors:</strong> Stop assuming junior devs have the same foundation you did. Be explicit about the “why” behind decisions. Create space for questions that might sound basic. Do code reviews that focus on understanding, not just functionality. Maybe assign “AI-free” tasks occasionally, not as hazing, but as skill-building.</p><p><strong>For companies:</strong> Normalize “I don’t know, let me learn this properly” instead of “ship at all costs.” Allocate time for learning, not just velocity. Celebrate understanding, not just output. Maybe reconsider how you evaluate technical skills in interviews—you’re not just testing if someone can code, you’re testing if they can think.</p><p><strong>For education:</strong> Stop pretending AI doesn’t exist. Teach people how to use it effectively, not how to avoid it. But also teach debugging, system design, and foundational concepts. The goal isn’t to reject AI; it’s to use it wisely while building real understanding.</p><h2 id="the-uncomfortable-non-conclusion">The Uncomfortable Non-Conclusion</h2><p>Here’s the truth: We’re all figuring this out in real-time. Every generation of developers has had this conversation in some form—about IDEs, about Stack Overflow, about frameworks that abstract away complexity. The old guard always worries the new guard doesn’t know “the fundamentals.”</p><p>Sometimes they’re right. Sometimes they’re just old. Also, “the fundamentals” is an ever shifting goal post.</p><p>I don’t know which this is yet. Ask me in five years when we see how this generation of AI-native developers performs at scale. Ask me when we see if they hit a ceiling or if they just built their skills differently.</p><p>What I do know is this: AI-assisted coding isn’t going away. The barrier to building software has collapsed. Junior devs can be productive faster than ever. And somewhere in there, we need to figure out how to preserve the understanding that makes you not just productive, but genuinely good at this job.</p><p>Because the best developers aren’t the ones who can generate code the fastest. They’re the ones who can look at a complex system, understand how it works, figure out why it’s broken, and know how to fix it. Whether you learned that through years of painful debugging or through AI-accelerated practice doesn’t really matter.</p><p>As long as you actually learned it.</p>
]]></content:encoded></item><item><title>How to Secure Your Vibe-Coded Project (Before It Secures You)</title><link>https://lakshminp.com/2025/10/how-to-secure-your-vibe-coded-project/</link><pubDate>Thu, 30 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/how-to-secure-your-vibe-coded-project/</guid><category>essays</category><category>security</category><description>Most developers ship AI-generated code without security audits. Here&amp;rsquo;s how to catch vulnerabilities before they become breaches—without hiring a security team.
You&amp;rsquo;re moving fast. AI is writing code. You&amp;rsquo;re shipping features daily. But speed creates blind spots—and security vulnerabilities love blind spots.
I&amp;rsquo;ve watched developers ship vibe-coded projects that worked but had SQL injection holes, exposed API keys, and broken authentication. Not because they were careless—because they were solo and didn&amp;rsquo;t have time to review every line AI generated.</description><content:encoded>&lt;![CDATA[<p>Most developers ship AI-generated code without security audits. Here&rsquo;s how to catch vulnerabilities before
they become breaches—without hiring a security team.</p><p>You&rsquo;re moving fast. AI is writing code. You&rsquo;re shipping features daily. But speed creates blind spots—and
security vulnerabilities love blind spots.</p><p>I&rsquo;ve watched developers ship vibe-coded projects that<em>worked</em> but had SQL injection holes, exposed
API keys, and broken authentication. Not because they were careless—because they were solo and didn&rsquo;t have
time to review every line AI generated.</p><p><strong>Here&rsquo;s the truth: you can&rsquo;t manually audit everything. But you can automate the audit.</strong></p><h2 id="why-incremental-reviews-arent-enough">Why Incremental Reviews Aren&rsquo;t Enough</h2><p>Tools like<code>/security-review</code> catch issues in pull requests. That&rsquo;s great for new code. But what
about legacy code? Configurations that haven&rsquo;t been touched in months? Dependencies with known CVEs?</p><p>Incremental reviews are daily vitamins. Full audits are annual physicals. You need both.</p><h2 id="what-a-security-audit-should-cover">What a Security Audit Should Cover</h2><p>A comprehensive security audit doesn&rsquo;t just scan for SQL injection. It evaluates your entire attack surface
against industry-standard frameworks:</p><ul><li><strong>OWASP Top 10 2021</strong> — Broken access control, cryptographic failures, injection attacks</li><li><strong>OWASP API Security Top 10 2023</strong> — Broken object-level authorization, mass assignment, security misconfigurations</li><li><strong>Cloud &amp; Infrastructure Security</strong> — Misconfigured S3 buckets, exposed environment variables, weak IAM policies</li><li><strong>Supply Chain Security</strong> — Vulnerable dependencies, outdated packages, insecure third-party integrations</li></ul><h2 id="the-four-layers-of-a-proper-audit">The Four Layers of a Proper Audit</h2><h3 id="1-reconnaissance-understanding-your-stack">1. Reconnaissance: Understanding Your Stack</h3><p>Before auditing, the tool needs to know what you&rsquo;re running: Node.js? Python? Docker? Postgres or MongoDB?
Framework: Express, FastAPI, Next.js?</p><p>This determines which vulnerability patterns to look for. SQL injection matters in Postgres apps. NoSQL injection
matters in MongoDB apps. Different stacks, different attack vectors.</p><h3 id="2-code-analysis-finding-hidden-vulnerabilities">2. Code Analysis: Finding Hidden Vulnerabilities</h3><p>This is where the audit scans every file—not just recent changes—looking for patterns that indicate security issues:</p><ul><li>User input flowing directly into database queries (SQL/NoSQL injection risk)</li><li>Hardcoded secrets or credentials in code</li><li>Missing authentication checks on sensitive endpoints</li><li>Weak cryptography (MD5, SHA1) for passwords or tokens</li><li>Overly permissive CORS policies</li><li>Exposed debug endpoints in production</li></ul><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// BAD: SQL injection vulnerability</span></span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'/user'</span><span class="p">,</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="nx">userId</span><span class="o">=</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="nx">db</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="sb">`SELECT * FROM users WHERE id =</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span><span class="c1">// Dangerous!</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="c1">// GOOD: Parameterized query</span></span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'/user'</span><span class="p">,</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="nx">userId</span><span class="o">=</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="nx">db</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="s1">'SELECT * FROM users WHERE id = ?'</span><span class="p">,</span><span class="p">[</span><span class="nx">userId</span><span class="p">]);</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><h3 id="3-configuration-review-infrastructure-security">3. Configuration Review: Infrastructure Security</h3><p>Code vulnerabilities are obvious. Configuration vulnerabilities are subtle:</p><ul><li>Are environment variables properly isolated?</li><li>Do Docker containers run as root? (They shouldn&rsquo;t.)</li><li>Are cloud storage buckets publicly accessible?</li><li>Is TLS enforced on all endpoints?</li><li>Are rate limits configured to prevent abuse?</li></ul><p>These issues don&rsquo;t show up in code reviews. They live in config files, environment variables, and infrastructure settings.</p><h3 id="4-dependency-scanning-supply-chain-vulnerabilities">4. Dependency Scanning: Supply Chain Vulnerabilities</h3><p>Your code might be secure, but your dependencies might not be. Audit tools scan<code>package.json</code>,<code>requirements.txt</code>, and<code>go.mod</code> against databases of known CVEs (Common Vulnerabilities and Exposures).</p><p>If a package has a critical security flaw, you&rsquo;ll know—and you&rsquo;ll get specific remediation guidance (upgrade to version X.Y.Z).</p><h2 id="how-to-run-an-effective-security-audit">How to Run an Effective Security Audit</h2><p>If you&rsquo;re using Claude Code, you can install a<code>/security-audit</code> slash command that automates this entire process.
It performs reconnaissance, analyzes every file, reviews configurations, and scans dependencies—generating an actionable report.</p><p>The key difference from incremental reviews:<strong>it catches everything</strong>—even vulnerabilities in code you wrote
months ago and haven&rsquo;t touched since.</p><h3 id="installation">Installation</h3><p>Global installation (available across all projects):</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">bash</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/.claude/commands</span></span><span class="line"><span class="cl">curl -o ~/.claude/commands/security-audit.md https://example.com/security-audit.md</span></span></code></pre></div></div><p>Project-specific installation (for team collaboration):</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">bash</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p .claude/commands</span></span><span class="line"><span class="cl">curl -o .claude/commands/security-audit.md https://example.com/security-audit.md</span></span></code></pre></div></div><h3 id="running-the-audit">Running the Audit</h3><p>From Claude Code, type:</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">bash</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/security-audit</span></span></code></pre></div></div><p>The tool will:</p><ol><li>Identify your tech stack</li><li>Scan every file for vulnerability patterns</li><li>Review configurations (Docker, env files, cloud settings)</li><li>Check dependencies against CVE databases</li><li>Generate a prioritized report with specific remediation steps</li></ol><h2 id="when-to-audit">When to Audit</h2><p><strong>Use<code>/security-review</code> during daily development</strong> for fast feedback on pull requests.</p><p><strong>Use<code>/security-audit</code> monthly or quarterly</strong> for comprehensive assessment—especially before major releases
or when adding new features that touch sensitive data.</p><p>Think of them as complementary: one catches new issues, the other catches everything.</p><h2 id="the-20-that-prevents-80-of-breaches">The 20% That Prevents 80% of Breaches</h2><p>Most security incidents aren&rsquo;t sophisticated zero-days. They&rsquo;re basic misconfigurations: exposed API keys, missing authentication,
unpatched dependencies.</p><p>A security audit catches these low-hanging issues—the 20% of configs that prevent 80% of breaches. You&rsquo;re not trying to build
Fort Knox. You&rsquo;re trying to avoid being the easy target.</p><h2 id="security-is-a-discipline-not-a-feature">Security is a Discipline, Not a Feature</h2><p>When you&rsquo;re running solo, security feels like something you&rsquo;ll &ldquo;get to later.&rdquo; But later turns into never—until something breaks.</p><p><strong>Automate the audit. Run it regularly. Fix what it finds.</strong></p><p>You don&rsquo;t need a security team. You need visibility into what&rsquo;s broken and specific guidance on how to fix it. That&rsquo;s what a
proper security audit provides.</p><p>Because the best time to find vulnerabilities is before attackers do.</p>
]]></content:encoded></item><item><title>If I Were Starting a New SaaS Today, I'd Do This</title><link>https://lakshminp.com/2025/10/if-i-were-starting-a-saas-today/</link><pubDate>Wed, 15 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/if-i-were-starting-a-saas-today/</guid><category>essays</category><category>saas</category><description>Most SaaS projects fail because founders spend weeks building scaffolding instead of features. Here&amp;rsquo;s how to skip the boilerplate and ship fast.
I&amp;rsquo;ve built half a dozen SaaS products. Some succeeded. Most failed. But the failures taught me something critical: ideas don&amp;rsquo;t die from competition—they die from delayed launches.
You lose weeks setting up databases, authentication, APIs, file uploads, and admin panels before writing a single line of actual product code. By the time you&amp;rsquo;re ready to ship, momentum is gone.</description><content:encoded>&lt;![CDATA[<p>Most SaaS projects fail because founders spend weeks building scaffolding instead of features. Here&rsquo;s how to skip the boilerplate and ship fast.</p><p>I&rsquo;ve built half a dozen SaaS products. Some succeeded. Most failed. But the failures taught me something critical:<strong>ideas don&rsquo;t die from competition—they die from delayed launches</strong>.</p><p>You lose weeks setting up databases, authentication, APIs, file uploads, and admin panels before writing a single
line of actual product code. By the time you&rsquo;re ready to ship, momentum is gone.</p><p>If I were starting today, I&rsquo;d skip all that. I&rsquo;d use Supabase—and I&rsquo;d ship an MVP in days, not months.</p><h2 id="the-problem-with-building-from-scratch">The Problem with &ldquo;Building from Scratch&rdquo;</h2><p>Building foundations feels productive. You&rsquo;re writing code, making decisions, setting up infrastructure. But you&rsquo;re
not building anything users can touch.</p><p>Auth alone consumes days: password hashing, session management, password resets, email verification. Then you need
database migrations, API routes, input validation, error handling. Before you know it, you&rsquo;ve burned two weeks on
scaffolding.</p><p><strong>That&rsquo;s two weeks you could&rsquo;ve spent validating whether anyone actually wants what you&rsquo;re building.</strong></p><h2 id="why-supabase-changes-everything">Why Supabase Changes Everything</h2><p>Supabase isn&rsquo;t just a database. It&rsquo;s a complete backend—authentication, storage, real-time updates, edge functions—packaged
as a single platform. And unlike Firebase, it&rsquo;s built on PostgreSQL, so you&rsquo;re not locked into proprietary tech.</p><h3 id="postgresql-foundation">PostgreSQL Foundation</h3><p>Every table you create automatically gets REST and GraphQL APIs. No backend needed. Query directly from your frontend
with row-level security enforcing permissions at the database layer.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Fetch user's tasks directly from the frontend</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="p">{</span><span class="nx">data</span><span class="p">,</span><span class="nx">error</span><span class="p">}</span><span class="o">=</span><span class="kr">await</span><span class="nx">supabase</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="s1">'tasks'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="s1">'*'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">eq</span><span class="p">(</span><span class="s1">'user_id'</span><span class="p">,</span><span class="nx">userId</span><span class="p">);</span></span></span></code></pre></div></div><p>You still get full PostgreSQL power: triggers, extensions, stored procedures, joins, indexes. It&rsquo;s not a toy database—it&rsquo;s
enterprise-grade Postgres with a developer experience that doesn&rsquo;t suck.</p><h3 id="authentication-that-just-works">Authentication That Just Works</h3><p>Built-in support for email/password, magic links, OTPs, OAuth (Google, GitHub, etc.), and custom SSO. User records live
in your Postgres schema. Add custom fields. Create relationships. No vendor lock-in.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Sign up with email/password</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="p">{</span><span class="nx">user</span><span class="p">,</span><span class="nx">error</span><span class="p">}</span><span class="o">=</span><span class="kr">await</span><span class="nx">supabase</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">signUp</span><span class="p">({</span></span></span><span class="line"><span class="cl"><span class="nx">email</span><span class="o">:</span><span class="s1">'user@example.com'</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nx">password</span><span class="o">:</span><span class="s1">'secure-password'</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="c1">// Magic link (passwordless)</span></span></span><span class="line"><span class="cl"><span class="kr">await</span><span class="nx">supabase</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">signInWithOtp</span><span class="p">({</span></span></span><span class="line"><span class="cl"><span class="nx">email</span><span class="o">:</span><span class="s1">'user@example.com'</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>No JWT libraries. No session stores. No password reset flows. It&rsquo;s handled. You write product code.</p><h3 id="real-time-updates-without-redis">Real-Time Updates Without Redis</h3><p>WebSocket-based subscriptions give you instant updates on table changes. No message brokers. No Kafka. No Redis pub/sub.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Subscribe to new messages</span></span></span><span class="line"><span class="cl"><span class="nx">supabase</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">channel</span><span class="p">(</span><span class="s1">'messages'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'postgres_changes'</span><span class="p">,</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nx">event</span><span class="o">:</span><span class="s1">'INSERT'</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nx">schema</span><span class="o">:</span><span class="s1">'public'</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nx">table</span><span class="o">:</span><span class="s1">'messages'</span></span></span><span class="line"><span class="cl"><span class="p">},</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'New message:'</span><span class="p">,</span><span class="nx">payload</span><span class="p">.</span><span class="k">new</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">})</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">subscribe</span><span class="p">();</span></span></span></code></pre></div></div><p>Insert a row in your messages table? All connected clients receive it instantly. Build chat, notifications, live dashboards—without
standing up infrastructure.</p><h3 id="row-level-security">Row-Level Security</h3><p>Database-level authorization policies replace entire backend authorization layers. One policy line defines who can access what.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">sql</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Users can only see their own tasks</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"/><span class="n">POLICY</span><span class="w"/><span class="s2">"Users see own tasks"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">ON</span><span class="w"/><span class="n">tasks</span><span class="w"/><span class="k">FOR</span><span class="w"/><span class="k">SELECT</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">USING</span><span class="w"/><span class="p">(</span><span class="n">auth</span><span class="p">.</span><span class="n">uid</span><span class="p">()</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">user_id</span><span class="p">);</span></span></span></code></pre></div></div><p>Policies compose. Multi-tenant? Add tenant_id checks. Admin override? Add role conditions. Security moves from scattered
backend checks to centralized, auditable rules.</p><h3 id="storage--edge-functions">Storage &amp; Edge Functions</h3><p>Native file upload handling with access rules compatible with row-level security. TypeScript-based edge functions deploy
in seconds for webhooks, scheduled jobs, or integrations.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Upload file with automatic access control</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="p">{</span><span class="nx">data</span><span class="p">,</span><span class="nx">error</span><span class="p">}</span><span class="o">=</span><span class="kr">await</span><span class="nx">supabase</span><span class="p">.</span><span class="nx">storage</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="s1">'avatars'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">upload</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">/avatar.png`</span><span class="p">,</span><span class="nx">file</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="c1">// Edge function for webhook processing</span></span></span><span class="line"><span class="cl"><span class="kr">import</span><span class="p">{</span><span class="nx">serve</span><span class="p">}</span><span class="nx">from</span><span class="s1">'https://deno.land/std/http/server.ts'</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="nx">serve</span><span class="p">(</span><span class="kr">async</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="nx">payload</span><span class="o">=</span><span class="kr">await</span><span class="nx">req</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="c1">// Process webhook</span></span></span><span class="line"><span class="cl"><span class="k">return</span><span class="k">new</span><span class="nx">Response</span><span class="p">(</span><span class="s1">'OK'</span><span class="p">,</span><span class="p">{</span><span class="nx">status</span><span class="o">:</span><span class="mi">200</span><span class="p">});</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><h2 id="the-developer-experience-you-deserve">The Developer Experience You Deserve</h2><p>The CLI, dashboard, SQL editor, and APIs feel cohesive. You&rsquo;re not juggling five different tools with five different
authentication methods. Everything integrates.</p><p>Need to see your database? Open the dashboard. Want to test a query? Use the SQL editor. Ready to deploy a function?<code>supabase functions deploy</code>. It just works.</p><h2 id="open-source--portability">Open Source &amp; Portability</h2><p>Unlike Firebase, Supabase runs self-hosted via Docker Compose. Start on their hosted platform. Move to self-hosted
if you outgrow it. Same codebase. Same developer experience.</p><p>You&rsquo;re not locked in. Your data is Postgres. Your auth is Postgres. Your files are S3-compatible storage. If Supabase
disappears tomorrow, you can migrate. Try doing that with Firebase.</p><h2 id="ship-fast-own-your-stack-avoid-unnecessary-complexity">Ship Fast, Own Your Stack, Avoid Unnecessary Complexity</h2><p>This is the indie developer playbook: start small, ship fast, scale naturally. Supabase embodies that philosophy.</p><p>You&rsquo;re not choosing between a custom backend and a proprietary platform. Supabase sits in the middle—powerful enough
for serious applications, simple enough to start with one table and an auth flow.</p><p><strong>If I were starting a SaaS today, I&rsquo;d skip the scaffolding. I&rsquo;d use Supabase. And I&rsquo;d ship in days—not weeks.</strong></p><p>Because the best way to validate an idea isn&rsquo;t to build perfect infrastructure. It&rsquo;s to put something in front of users
and learn whether they care.</p><p>Supabase gets you there faster. And when you&rsquo;re running solo, speed is everything.</p>
]]></content:encoded></item><item><title>AWS Is Overrated</title><link>https://lakshminp.com/2025/10/aws-is-overrated/</link><pubDate>Sat, 11 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/aws-is-overrated/</guid><category>essays</category><category>kubernetes</category><description>If you’re an indie dev building your first SaaS, AWS is not your friend.
It’s a maze of services, dashboards, and acronyms pretending to make you productive while quietly billing you for curiosity.
Sure, it’s “the industry standard.” But here’s the thing: you’re not Netflix. You’re not Stripe. You don’t need fifteen managed services to ship an MVP. You just need one working prototype in front of users.
When I started shipping my own SaaS projects, I defaulted to AWS too. Everyone said it was the “serious” choice. I spun up EC2s, tinkered with VPCs, IAM roles, and CloudWatch dashboards.</description><content:encoded>&lt;![CDATA[<p>If you’re an indie dev building your first SaaS, AWS is not your friend.</p><p>It’s a maze of services, dashboards, and acronyms pretending to make you productive while quietly billing you for curiosity.</p><p>Sure, it’s “the industry standard.” But here’s the thing: you’re not Netflix. You’re not Stripe. You don’t need fifteen managed services to ship an MVP. You just need one working prototype in front of users.</p><p>When I started shipping my own SaaS projects, I defaulted to AWS too. Everyone said it was the “serious” choice. I spun up EC2s, tinkered with VPCs, IAM roles, and CloudWatch dashboards.</p><p>Two weeks later, my app still wasn’t live. But my bill was.</p><p>That’s when it clicked. AWS is optimized for<em>scale,</em> not<em>speed.</em> It’s designed for teams with DevOps pipelines, budgets, and compliance officers. Indie devs have none of those.</p><p><strong>Here’s the real problem:</strong></p><blockquote><p>AWS makes you<em>feel</em> productive because it has a service for everything.</p></blockquote><p>But it slows you down because you end up<em>assembling</em> infrastructure instead of shipping software.</p><p>You’re busy wiring VPCs while your users are waiting for a login page.</p><p>If you’re building your first SaaS, you’re better off with:</p><ul><li><p><strong>Render</strong> or<strong>Fly.io</strong> for fast deploys.</p></li><li><p><strong>Railway</strong>,<strong>Supabase</strong> if you love simplicity.</p></li><li><p>DigitalOcean app platform</p></li><li><p>Or even your own<strong>K3s</strong> box on a $30 DigitalOcean droplet if you like to tinker.(More on this in future posts)</p></li></ul><p>You’ll have full control, predictable costs, and a deploy story you can explain in a single sentence.</p><p>That’s what matters at your stage — not five-nines availability across three regions.</p><p>AWS will always have its place. It’s incredible at running serious workloads, regulated systems, and multi-tenant platforms at scale.</p><p>But for indie devs trying to launch, learn, and iterate fast — it’s<em>overkill.</em></p><p>Use the simplest stack that lets you ship.</p><p>Add complexity only when success forces you to.</p><p>Because nothing kills momentum faster than debugging IAM policies instead of building features.</p><h2 id="tldr">TL;DR</h2><p>If you’re a solo founder or small team, your advantage isn’t scale — it’s speed.</p><p>Don’t trade that away for a cloud that was never built for you.</p><p>I share one short post daily-ish for productive indie developers — how to ship faster, cheaper, and saner. Subscribe if that’s your vibe.</p>
]]></content:encoded></item><item><title>AI Can Build Anything—Except Product Taste</title><link>https://lakshminp.com/2025/10/ai-product-taste/</link><pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/ai-product-taste/</guid><category>essays</category><category>craft</category><description>Everyone says AI makes you “10x more productive.” I’m not sure about that. What it actually made me is… more deliberate.
When execution is basically free, the bottleneck shifts. It’s no longer can I build this? It’s should I build this?
That sounds obvious, but most of us (me included) are terrible at it. We confuse motion for progress. And AI just cranks the treadmill speed up to 11.
Here’s what I mean.</description><content:encoded>&lt;![CDATA[<p>Everyone says AI makes you “10x more productive.” I’m not sure about that. What it actually made me is… more deliberate.</p><p>When execution is basically free, the bottleneck shifts. It’s no longer<em>can I build this?</em> It’s<em>should I build this?</em></p><p>That sounds obvious, but most of us (me included) are terrible at it. We confuse motion for progress. And AI just cranks the treadmill speed up to 11.</p><p>Here’s what I mean.</p><p>The other weekend, I let myself play around with the idea of “AI-generated developer dashboards.” Normally, something like that would eat a week of evenings. This time, I had three versions running before breakfast: a React prototype, a Python backend spitting out metrics, and a half-decent mock landing page.</p><p>Impressive? Maybe. Useful? Not really. By Sunday night I realized I’d basically built three beautifully useless toys. Execution had been trivial. The problem was never execution—it was me chasing shiny objects.</p><p>That’s the AI paradox. It lowers the cost of building so much that the real scarcity becomes<em>taste</em>. Judgment. The ability to say no.</p><p>Because here’s the dark side: the opportunity cost of distraction just went up. Before, if I burned a week tinkering on something dumb, at least I learned a few low-level tricks. Now I can burn a week and end up with a full microservice, a CI/CD pipeline, and a Terraform config… for an idea that didn’t deserve any of it. Congratulations, I’ve industrialized my dead ends.</p><p>I’ve caught myself doing this with infrastructure experiments, too. AI will happily generate Kubernetes manifests, Helm charts, and CI workflows for whatever hair-brained service I throw at it. The code even looks plausible at first glance. Then I deploy it, watch it explode, and realize the whole thing never needed to exist in the first place. It’s the most polished waste of time imaginable.</p><p>And this is why restraint has suddenly become a superpower. The real work isn’t generating more; it’s filtering harder. AI will give you 50 rabbit holes before lunch. If you’re not ruthless about which one you go down, you’re just automating your own distraction.</p><p>The old mantra was “ship fast and break things.” AI makes that easier than ever. But there’s a hidden multiplier effect: fast execution with bad strategy doesn’t just fail—it fails<em>louder</em>. You don’t just waste time, you waste time at scale. Meanwhile, the teams with clear strategy and discipline can use the exact same tools to compound wins. Same technology, wildly different outcomes.</p><p>This is why I think “thinking” has quietly become underrated. Tinkering used to be the path to learning. Now tinkering is dangerous. You can dig a perfect hole in the wrong place faster than ever. Spending more time deciding where to dig—that’s the skill worth leveling up.</p><p>Developers don’t usually like to hear that. We want to build. But in an AI-first world, the rarest and most valuable act might be…<em>not</em> building. Closing the tab. Saying no to the prototype. Choosing boredom over the dopamine hit of “look what I got running.”</p><p>So no, AI didn’t make me more productive. It made me picky. It forced me to care about what I was building in the first place.</p><p>And that’s the paradox: AI made execution trivial, so the premium is now on taste, judgment, and focus.</p><p>If you can’t decide what matters, AI will happily help you drown in what doesn’t.</p>
]]></content:encoded></item></channel></rss>