There he stands, staring at me with loyal, shell-shocked eyes. A yak. A shaved yak, to be precise. And the yak has no real idea what just happened to him.

Where did this shave begin? I had the idea one evening. I’ve been using Jekyll for almost 5 years now. My website is entirely static. There are lots of .html files, images, a few fonts, some CSS that falls apart in more or less spectacular fashion at the slightest attempt at modification. No php, no database.

The “source” lives in a Gitea instance and a Gitea Action rebuilds the blog with every change I commit. The whole thing. Uploads it. I’ve built myself a nice two-and-a-half-stage process for this.1 It all works really well. I enjoy working with it. There’s just one problem!

Jekyll doesn’t provide a comment function. How could it? There’s no simple way to give a user the ability to contribute content in the form of a comment to the blog. There are solutions that implement comments externally. Disqus, for example. You embed it via Javascript into the static page. But I didn’t want to use that. If only because it costs money. I want my blog to be able to keep running at minimal cost whenever I kick the bucket someday. Yeah, a serious surgery makes you think about your digital estate.2

I also make an effort to ensure that all resources used on http://www.c0t0d0s0.org come exclusively from this server and don’t generate logfiles somewhere else. That’s why I’ve gradually “localised” all the fonts.

Now, I like using Mastodon as a social network. And it’s obvious that you could use it as a comment platform. It already comes with many of the features you’d expect from a blog comment system. And that’s how I’d been using it since last year. I managed this with the help of a Javascript script that fetched, on the client side, the toots descending from a root toot. It then rendered them client-side and the browser displayed them.3

This was not without its problems: it meant I was dependent on an external server. Server gone, comments gone. More importantly: I was offloading work for every single request for a blog page with comments onto the server of a stranger who didn’t quite know about his good fortune. Not exactly fair either. And on top of that, the whole thing left log data not only on my server, but also on another server that isn’t administered by me. All in all, a rather unhappy state of affairs.

Then I had the following idea: Why don’t I just fetch the toot locally, have it rendered nicely into static HTML, and call it done. Since I’d have liked to moderate the toots appearing on my blog anyway4, it was unproblematic that new toots wouldn’t show up immediately.

And that’s where the problems began. I was sitting on my sofa with a cup of tea and a notepad, thinking about everything I’d need for this.

Extract the necessary data (like: which toot is the root toot for this article) from the Markdown files. Fetch toots. Generate HTML. All obvious.

But: built into the build pipeline means that if I place a forgotten full stop somewhere, new build, all toots get regenerated. That was completely uncritical when it was just about resources on my own build server. With several hundred toots, that means several hundred requests to my Mastodon server in a short time.

So I needed a toot cache (not to be confused with a toothache right before the tooth fairy arrives). The idea: as long as three hours haven’t passed since the HTML fragment was generated, the script leaves the file alone and doesn’t regenerate it.

Okay, but then I noticed that I was still hitting the Mastodon server for the avatars. Those requests had to go local too. Every toot has some kind of avatar. In my case, currently the picture with the highlighter-yellow jacket. So I had to download the avatars and redirect the display in the toots to this local copy on my web server. An avatar cache! I need an avatar cache!

Next problem: some of my toots had images that also came from the Mastodon server. Hmm … I think I need yet another cache. A media cache to the rescue!

Caches? Caches want to be cleaned up. Otherwise they end up looking like my garage. For layout changes I just want to regenerate quickly without potentially making hundreds of API calls. The Mastodon servers send headers for rate limiting purposes. Those want to be interpreted. One challenge after another.

AARRRRRGHHHHHHH!

After this outcry, as I dove deeper and deeper into the problem, it became increasingly clear that I would either spend the next few weeks’ evenings in front of the computer. Or I’d hack together something quick and dirty that, alongside actually doing the job, would also pile up technical debt.

Every idea, every realisation that doing A meant I also had to do B, and I might have to safeguard B’s work with C, led to yet more challenges. And it went on: B in turn depended on D and E. It truly felt like a herd of yaks was lining up for a shave.

Now, my blog does matter to me. It’s slowly approaching 25 years. But weeks? For a problem that was basically already solved? Where I just didn’t quite like the solution? I still have plenty of other projects on my list. A lot got left on the table in the second half of 2025 and is staring at me accusingly. I really couldn’t afford to spend that much time on this.

But I didn’t want to bodge it either. And hoping that someone had already written something like this and I could just extract the essential bits from it — that wasn’t going to work out either.

So I thought to myself: give vibecoding a try! Yes, I know. About a year and a half ago I abandoned a private project that used one of those code generators. The result was downright ghastly.

But some time had passed since then. And before you open your big mouth and get worked up about something, you should at least be up to date. Give it a try, see how it’s evolved. What’s the worst that could happen? If it’s no good, I can always fire up vi.

And so, on an AI basis, the program came into being that transforms Mastodon threads into a static, server-side includable version.5

The result, from my perspective, turned out really well. It does exactly what I want, as far as my test cases go. The code looks quite decent. It feels different from how I would write it. But so would the code of any other Python user. I recognise my ideas, see patterns I would implement similarly. But the code just feels foreign.

It was worth it. The static toots look quite presentable. The users stay out of other servers’ logs. And it loads really fast.

Having written earlier that the whole thing would probably have kept me busy for weeks of evenings in conventional development, there’s naturally still an open question: how long did the vibecoding take? 2 evenings … roughly 8 hours.

As I sat there, entering prompt after prompt, a few things struck me.

It’s quite easy to whip up features on the fly, and you tend to do exactly that. Where with hand-written code you’d have long since said “runs. fine. stays. not pretty. but does what it should,” you keep building in more and more things that you’d technically need to build by hand too, but just don’t. For the limited use case, even an incomplete implementation works without going up in flames.

If I can describe the “proper” rate limiting in maybe 20–30 words and the AI spits out fairly decent code for it, then you go ahead and do that anyway rather than leaving it at simple code that just waits a second so the Mastodon server operator doesn’t get a shock when you kick off a generator run.

And before you know it, you’ve arrived at Brazilian waxing for yaks. A little bell here, a little whistle there.

The other observation: programming is a craft, and like every craftsperson, you form a bond with your product. Not with every one. The three-hundredth turned piece of steel won’t trigger that. But the really complicated things. The ones that stretch your abilities.

The same goes for code, in my observation. People say “refactor mercilessly.” But you still cling to code you sacrificed nights for. You consider some lines genius, even though in their complexity they’re really just a tangled, poorly maintainable product of sleep deprivation and caffeine underdosing. Something you won’t understand yourself in 5 years, once the memory of that flash of brilliance has completely vanished.

Over the years I’ve developed quite a bit of software and scripts. From a heating controller (when the electronics of an oil furnace died on me over a weekend and I had to keep it alive for another month. These things never happen in August.) to all kinds of scripts that process data of one sort or another. I’ve often noticed that I held on to code for far too long, had no desire to throw it away and rebuild, to make things easier for myself, to make them better.

With AI code generation you have absolutely zero attachment to the code. You’ll casually throw away 1000 lines of code without shedding a tear, because you’ve simply found a better prompt that addresses the problem much more elegantly.

A comparison with books came to mind. The majority of literature isn’t brilliant, not even very good. It’s just okay. And in many places, that’s simply enough. I don’t want to be invited to Stockholm for my documentation. I think it’s quite similar with programming. The majority of programs aren’t brilliant, most of them perform some mundane task. Glue code to bind together things that don’t want to be bound together. Some converter job. I wouldn’t want to see the flight control system for the next offering from Airbus programmed this way. Not even the windscreen wiper control unit in my car.

But a converter job (even if it’s somewhat more complex) running in a container that gets thrown away after every run? Working on a copy of the markdowns that gets freshly provided via git each time? That seems like a good target for gaining experience with this method. If things go wrong, a blog goes dark. And the world thinks: so what?

Perhaps the most important insight: yes, vibecoding is very fast … but you spend a lot of time formulating the prompts so that something reasonably sensible comes out more or less deterministically everywhere. In other words, fundamentally the same thing that takes a lot of time in normal coding too: using pliers to wrest a complete specification from whoever you’re writing the software for.

Particularly for programming, an observation of mine seems to apply: prompt engineering is the art of the leading question. Formulating a prompt — the question — such that the answer may vary in its details, but ultimately can only go in one direction. That is no small amount of work.

I learned COBOL from my father a long time ago. I still know it today, even if my knowledge is heavily covered by the patina of disuse.6 I remember a book that said, in essence, that COBOL was meant to resemble natural language in order to simplify writing and reading programs. I asked myself even back then, “Who talks like that?”

Perhaps AI will fulfil this promise that COBOL once made. It offers the possibility of translating natural language into the solution of a problem. Perhaps AI is nothing more than simply a kind of preprocessor. Before high-level languages existed, the industry programmed in assembly. And being able to write really large programs in assembly was not exactly a widespread skill back then, and still isn’t today.

There is, of course, a significant difference. A preprocessor will always produce the same result. An AI will not — or not necessarily. The result depends on a multitude of conditions.

I suspect that something similar awaits us if AI code generation catches on. Python could acquire the same aura that assembly has today. You know it lurks behind the prompts. You can sort of see the big picture, but basically there’s a sign that reads “Here be dragons. Please keep away.”

I do see the problem, of course: if everyone stays away from dragon territory, how do you teach the venerable art of dragon slaying? If everyone is just sitting at prompts, who teaches the handling of the programming language underneath? Who teaches the AI the programming language? That, however, is a far more fundamental question. It applies to every domain where AI is used.

We will continue to need programming languages. I sincerely hope I never live to see the day when vibepreters — prompts that are simply executed directly — become more than a gag, more than a thought experiment. Even in my modest programming career I’ve been through enough Heisenbugs, race conditions and deadlocks that I have no desire to add the “mood” of an LLM to my debugging considerations. Especially when code that ran reliably for a long time suddenly stops doing so. We will presumably need programming languages for a long time yet that lift the implementation out of the stochastic and into the strictly deterministic world, where — bugs aside — you can predict with certainty what will happen when something is written in the code. Something I check into git and that will still produce the same result in 10 years.

I only know one thing right now: for me, AI code generation is a truly effective yak shaver.

Addendum

If you’d like to have a look at the result: I’ve uploaded it to Github, along with AI-generated documentation.


  1. “prod” for what you’re currently seeing, “draft” for what you will see but aren’t supposed to see yet. And “staging” for what you will see but can already look at, just not in production. In practice I’ve only used the first two variants so far. 

  2. I hope that at some point I’ll find someone to take over hosting the HTML pages. 

  3. Which I defined in the frontmatter section of the article’s Markdown file. 

  4. Something the Javascript solution couldn’t do either … 

  5. I deliberately don’t specify which AI I used. If I write “the one from Oracle,” my text could be misconstrued as advertising. If I write “a different one,” it raises the question of why I didn’t use the one from my employer. For my reflections here, it’s beside the point anyway. 

  6. But they’re still there … another call back to the easily learned past 

Mastodon · Comments
No comments yet. Be the first!
Written by

Joerg Moellenkamp

Grey-haired, sometimes grey-bearded Windows dismissing Unix guy.