# Can You Vibe Code an OS Without Lying to Yourself?

> Source: <https://dev.to/constant_itis/can-you-vibe-code-an-os-without-lying-to-yourself-2kfk>
> Published: 2026-09-16 05:51:12+00:00

I have no idea what I'm f*cking doing. Something I have been chewing on:

I am using AI agents to help build an operating system.

And I genuinely cannot tell you yet whether that is a good idea.

That is not false modesty. It is the actual state of things. [ShrekOS](https://dev.to/the_leon_odor/why-im-building-shrekos-when-containers-already-exist-1lg6) is real. It boots, it updates itself, it isolates agents. But a large amount of it was implemented by agents working against an architecture I defined. And every so often I stop and ask the uncomfortable question:

If I do not fully understand every line of this system, am I building an operating system, or am I just operating a machine that is building one for me?

I do not have a clean answer. So instead of pretending I do, let me beat the shit out of both sides.

Start with why this is scary.

Almost everything in an operating system is load-bearing in a way normal app code is not. Boot chain. Package trust. Permissions. Networking. Updates. Rollback. Hardware support. Sandboxing. Persistence. Installer behavior. Kernel assumptions.

A mistake in any of those does not give you a stack trace and a bad afternoon. It gives you a machine that will not boot. Or worse, one that boots and is quietly wrong in a way you do not notice until it matters.

Vibe coding a todo app is fine. If it breaks you notice, you fix it, nobody gets hurt. Vibe coding the thing that decides whether an update is trustworthy is a different category of decision. The blast radius is the whole machine.

So my instinct says this should be off-limits.

That question is boring and the answer is obviously yes. You can get an agent to emit plausible OS code all day.

The real question is sharper:

At what point does vibe coding stop being a productivity multiplier and start being irresponsible, because the system underneath you is too foundational to bullshit your way through?

That is the line I actually care about. And I do not think it sits where the loudest people on either side want it to.

Let me make the strongest version of the argument against myself.

None of those are strawmen. If that were the whole story, the responsible move would be to stop.

But the other side is also strong.

Nobody understands an entire modern OS stack anyway. Kernel, firmware, compiler, bootloader, userspace, microcode, all of it, in one head. That person does not exist. Every real OS is already built by people trusting layers they did not write and cannot fully audit.

Real engineering has always run on this. Abstraction. Tooling. Libraries. Reviewers. Specialists. You constrain the parts you cannot personally verify with the parts you can. Tests, types, interfaces, contracts.

And the things that constrain a human contributor constrain an agent just as well. Reproducible builds. Signatures. Isolation. Explicit threat models. An invariant does not care whether the code beneath it was written by a person having a bad day or a model having a confident hallucination. Reality either satisfies it or it does not. The hard part is proving which one happened.

An agent can also inspect far more implementation detail than I can hold in my head at once. Used correctly, that is not the danger. That is the tool.

So maybe the human job was never "write every line." Maybe it is "define the invariants and prove they hold." And maybe an OS is not disqualified from AI assistance. It just raises the bar on how real your proofs have to be.

I have one story that did not settle the argument, but it permanently changed my standard of evidence for it. And it is not hypothetical.

ShrekOS has a transactional A/B update path. A running system verifies the next release's signature, writes it into the inactive slot, reboots, and had better come up under the same signed boot chain as the one it replaced.

During dogfood, I built one release, staged the next one through that same update mechanism, and made the running system write it into the inactive slot and boot it. Not a mock. The real updater, the real slot.

Everything passed. The release existed. The signature verified. The version comparison worked. The updater selected the right slot and reported success. Every check above the disk was green.

Then it rebooted, and the new system would not boot.

Here is the part that matters:

The generated system was wrong.

The architecture review did not catch it.

The metadata tests did not catch it.

Actually booting the update caught it.

I had fed the update path a `.zst` artifact under the assumption that `systemd-sysupdate` would decompress it. It does not. It faithfully wrote the compressed bytes into the inactive slot, exactly as instructed. The slot began with a zstd header where a bootable filesystem should have been.

There was not even a buggy component. Every piece did its job correctly. The signatures were real, the metadata was real, the updater wrote precisely what it was handed. The assumption connecting those correct parts was wrong, and a wrong assumption between correct parts still composes into garbage. Every abstraction above the physical write reported health. The only thing that told the truth was the machine trying to boot the bytes.

I could have shipped that. It passed everything I had thought to check.

This is the whole piece, so I will say it plainly.

Vibe coding an OS is a terrible idea if "vibe coding" means trusting generated code because it looks right and the tests are green.

It gets a lot more defensible if "vibe coding" means using AI to implement against invariants you verify aggressively and without mercy.

Those are two completely different activities that happen to look similar from the outside. One is delegating judgment. The other is delegating labor while keeping the judgment.

The failure above is the proof, because it shows both. The composed system was trusted by every soft check. The hard invariant, the one that does not care about your feelings, was "the bytes the updater writes must boot under the verified chain." That one caught it.

The self-deprecation is not a bit. It is the safety mechanism, so let me be honest about where this still breaks.

The distinction only works if the invariants are the right ones. The bad update got caught because I happened to have a test that booted the real bytes. If I had only had the metadata tests, the ones that looked so thorough, I would have shipped a bricked update and felt great about it.

So the thing that actually keeps this honest is not the AI, and it is not the tests. It is a refusal. A refusal to call something proven because it passed the checks I found convenient to write.

"It boots" is not "it is correct." "The tests are green" is not "someone understands this." "The agent said it did the thing" is not "the thing happened."

Every one of those gaps is a place I could be lying to myself. The agents do not close them. I have to, by deciding what counts as proof and being ruthless about the difference between evidence and a verdict.

Honestly, both. And I think that is fine, on exactly one condition.

I do not write most of the lines anymore. I define what must be true, I decide what would prove it, and I refuse to accept anything softer than that proof. The agents move fast inside those walls. The walls are mine.

The day I stop being able to state the invariants, or stop being willing to enforce them the hard way, is the day this actually becomes stupid. Not because AI built it, but because at that point nobody is checking whether reality agrees.

The agents can write the code. They do not get to decide what counts as proof.
