The Bug That Kept Coming Back
The first sign something was wrong wasn't a crash. It was a pattern.
blockly-platform was the first real thing I built with Claude Code end to end - a Blockly-based platform for university programming exercises, driven entirely through Claude Code's Telegram channel. No editor open, no repo checked out on my machine, just a chat thread. I'd describe what I wanted, Claude Code would build it on a box I never looked at directly, and I'd judge the result by clicking around the deployed app.
On March 22nd, the home page came up empty. GET /api/exercises/published was returning 403. I said so in the chat; a few messages later, Claude Code said it was fixed - the endpoint hadn't been added to Spring Security's permitAll() list. I moved on, tried the category filter. Also empty, also 403, also missing from the same permitAll() list - same file, same class of fix, different line. Then the exercise detail page. Same story, third time, same day. Three days later, the like button stopped working - root cause, again: POST /api/exercises/*/like had never been whitelisted either. Four times, one file, one recurring gap.
None of these were hard bugs. Each one, in isolation, is a one-line fix a competent engineer makes without thinking twice. What bothered me, once I noticed the pattern, was that I hadn't noticed it as it happened. I had no diff to scroll through, no file to glance at and think "wait, didn't we just fix this exact class of thing twice already?" I had a chat log and a live app to poke at. The fourth fix looked, from where I sat, exactly like the first: a message telling me it was resolved.
That was the moment I started to suspect the problem wasn't the model. It was that nobody - not the model, not me - had anything to look at.
Why chat-only vibe coding breaks down
Here's what makes that pattern more interesting than "the AI made a mistake": every one of those four fixes was correct. Claude Code read the error, found the missing permitAll() entry, added it, and moved on - each time, in isolation, exactly the right diagnosis and exactly the right fix. Grade any single one of those four turns on its own and it passes. What failed wasn't a single decision. It was the absence of anything connecting the four decisions to each other.
A human engineer touching SecurityConfig.java for the third time in a week, over the same kind of hole, stops and thinks "let me just check the whole file" - not because they're smarter than Claude Code, but because they've been there before and remember it. Chat-only, Telegram-driven vibe coding doesn't give either party that continuity. Claude Code sees the current turn's error and fixes the current turn's error. I see a message saying "fixed" and move to the next thing on my list. Neither of us is holding the shape of the file over time, because neither of us is looking at the file - I'm looking at a phone screen, and the model's context is whatever's in that turn's window, not an accumulated sense that this file has a recurring problem.
That's the first layer. The second layer worried me more, because it doesn't have an obvious fix inside the chat-only model at all: some of Claude Code's decisions came back to me to approve. But I hadn't read the code structure - I'd never opened the repo, never seen SecurityConfig.java, never seen how exercises, submissions, and grading fit together. So when a decision did land in my lap, I was approving it with roughly the same information the model had: none, beyond the current message. "Human in the loop" only adds oversight if the human has something the model doesn't. Mine didn't.
To be clear about the boundary of this claim: none of it means chat-only driving is always wrong. For something small and throwaway, it's fine - there's nothing to lose track of. The failure mode shows up specifically as complexity and stakes climb, once a project has enough moving parts that "the shape of the file over time" is actually worth tracking.
The repo backs this up in ways that aren't just my memory of it. blockly-platform went from an empty repository to a merged, working platform in 80 commits over 23 days - fast, and it shows. There's no design doc anywhere in it, no architecture note, nothing that records why a decision was made - only a RUNBOOK.md, a TODO.md, and an ERROR_LOG.md that reads like a list of things discovered after the fact. A CLAUDE.md - the file that would have told Claude Code the project's own rules, things like "don't forget the security config when you add an endpoint" - didn't exist until April 2nd. Every dated entry in ERROR_LOG.md, all eighteen of them, including all four permitAll() misses, falls between March 22nd and March 28th. The guardrails arrived after the bugs they would have prevented, not before them.
The rebuild
The project that replaced it, programming-learning-platform, started the same day blockly-platform's pull request merged. That's not a coincidence I engineered - it's genuinely where the first project ended and the lesson from it started applying. It isn't a separate idea, either: its own CLAUDE.md describes it as a platform for Blockly and Python exercises, the same tool rebuilt from the ground up rather than a new one started from scratch.
This time I changed two things at once.
First, visibility: I moved to IntelliJ IDEA's remote development mode, with the project checked out and running on the same kind of remote box as before, but now with an actual editor attached to it - I could see the file tree, open a class, read a diff before it landed.
Second, process: I turned on the superpowers skill and wrote it into the project's CLAUDE.md as non-negotiable:
"Every task: Brainstorm โ Plan โ Implement (TDD). No skipping brainstorm. No code without a plan. No implementation before a failing test."
...with "No skipping Superpowers" listed again under the project's explicit red lines, in case that wasn't clear enough the first time.
The difference shows up in the repo's shape, not just in my memory of how it felt. programming-learning-platform is 458 commits deep and still active more than three months later. It has a real docs/ tree: a PRD, per-role user flows, an architecture doc, feature specs split by priority - and a docs/superpowers/plans/ directory holding forty-four dated implementation plans, running from within its first two weeks to earlier this month, each one a record of a brainstorm-and-plan step that happened before the corresponding code did. Where blockly-platform's only paper trail was a log of things that had already gone wrong, this one has a paper trail of decisions made on purpose, in order, before they were implemented.
It worked, in the sense that mattered most: I stopped approving things blind, because I finally had a diff to actually read and a plan to check the diff against.
It also had friction I didn't expect, and I want to be precise that this is my own experience with a specific setup at a specific time, not a verdict on the products themselves. IntelliJ IDEA's remote development client, even with the actual computation happening entirely on the server, still felt heavy on my local machine - noticeably more than I expected from something meant to be a thin window onto remote work. And the JetBrains Claude Code plugin had gaps that got in the way of the one thing visibility is supposed to buy you: clear communication. I couldn't attach an image to a message - no screenshot of a broken layout, no annotated diagram - so some things I could have shown in five seconds took several paragraphs to describe instead, with more room for the description to be wrong.
Visibility and process had fixed the real problem. The tool I'd wrapped them in was still fighting me.
The setup that stopped fighting me
A friend who'd been running a similar remote setup suggested I drop IntelliJ for VS Code partway through the project - specifically, VS Code's Remote-SSH extension to manage programming-learning-platform directly on the remote box, with the Claude Code extension running against that same connection, instead of a JetBrains-specific remote protocol and its own plugin.
Nothing about the visibility or the process changed, and that's the point: the file tree was still there, diffs still landed in front of me before I approved anything, and superpowers' brainstorm-plan-TDD loop carried over untouched, because it lives in the project's CLAUDE.md, not in whichever editor happens to be open.
What changed was the tool wrapped around both of those things. The client was lighter - it stopped being noticeably heavy on my local machine the way IDEA's had been. And the Claude Code extension let me attach a screenshot directly to a message, which matters more than it sounds for a platform built around a visual block editor and a grading UI: a broken Blockly toolbox or a misaligned grading panel is a five-second screenshot and a two-word question, not three paragraphs trying to describe a layout in words.
I've written before about the mechanics of why this specific combination - VS Code's Remote-SSH plus the Claude Code extension - is worth trusting with long-running, walk-away-able work: the process survives your laptop sleeping, and the compute never touches your local machine. I won't re-run that ground here. What matters for this piece is narrower: this was the first setup where visibility, process, and the tool itself all pulled in the same direction, instead of two out of three fighting the third.
The last mile
With visibility and process both solid, the last piece was the one that let me actually step away: knowing when Claude Code needed me, and being able to answer without being at my desk.
The when is claude-code-notify, a tool I built after getting tired of either babysitting a terminal or discovering twenty minutes later that a long TDD-driven turn had been sitting blocked on my sign-off the whole time. It pings me on Telegram at exactly the moments that matter - a turn is genuinely finished, background work included, or it needs my input, or it errored out.
The how is Remote Control (/remote-control), which lets me approve prompts and answer questions from the Claude app on my phone instead of reopening a laptop. Get pinged that Claude needs me, answer from my pocket - that's the workflow, and I've written separately about the one way it can quietly fail if the underlying remote session doesn't survive long enough to still be there when you answer.
It's worth being precise about what this layer is and isn't. It isn't what fixed the permitAll() problem - visibility and superpowers' plan-before-code discipline did that. What notify and Remote Control add is the ability to be away from the keyboard without losing either one: I still see the diff, Claude Code still follows the plan, I just don't have to be sitting there for either to keep happening.
Bolt the same walk-away layer onto the Telegram-only setup from the first project, and it wouldn't have fixed anything - you'd just get notified faster that a decision you couldn't evaluate had already been made. The order matters: visibility and process first, walk-away convenience on top of that foundation, not instead of it.
The bug, one more time
I sometimes wonder what would have happened if the fourth permitAll() miss - the one on the like button - had happened under the later setup instead of the first one.
My honest guess: it still would have gotten written, because forgetting a security rule on a new endpoint is an easy thing for anyone, human or model, to do once. But it wouldn't have taken three prior occurrences to catch, because a plan step would have said "add the endpoint and its security rule" as one unit, and a diff would have shown both halves - or shown only one, right there, before I approved it.
The bug wouldn't have needed a human to notice a pattern across a week of chat messages. It would have needed a human, or a model, to notice a gap in one diff - a much smaller ask, and one that visibility actually makes possible.
That's the whole shift, underneath all the tool names. Pure chat-only vibe coding doesn't fail because the model is bad at its job - every individual permitAll() fix in blockly-platform was correct. It fails because past a certain size, nobody involved, model or human, has enough in front of them to notice the second time a mistake happens, let alone stop it before the fourth.
Visibility and a process that makes you plan before you build don't make Claude Code smarter. They give whoever's watching, model or human, something to actually watch.
Comments
No comments yet. Start the discussion.