I gave an LLM root and lived to write about it
I handed a language model root on my gaming PC. It restarted my network to investigate the packet loss it had just caused. Hard lessons followed.
It started, as these things do, with a sentence I would later regret: “what if it could just run the commands itself?”
I’d just moved off Windows onto Pop!_OS, and I wanted a co-pilot for the parts of Linux I didn’t know yet — the kind of thing where you say “my Bluetooth headset doesn’t sound right” and something competent goes and scans bluetoothctl, greps journalctl -u bluetooth, finds the dropped connection, checks for a driver issue, and fixes it. The model is more than capable of that. The commands aren’t hard. So how much further could you push it?
The honest answer is: much too far, and I have the burnt weekend to prove it. This is the field report — the rabbit hole I went down, the chaos I caused on the one machine I actually wanted to relax on, and the guardrails I’d never hand a model root without again.
The model is the easy part
Here’s the thing nobody tells you when you start: natural-language system administration works. “Configure this box as an Apache host” is a solved translation problem. A decent model turns that into the right sequence of apt install, a2ensite, systemctl calls without breaking a sweat. The intelligence is basically free.
Which is exactly the trap. Because if the model is the easy part, your brain quietly concludes the whole thing is easy. It isn’t. Everything that matters — everything that decides whether you keep your filesystem — lives in the layer between the model and the kernel: validation, atomicity, rollback, audit, and a hard sense of what should never, ever be a single tool call.
Down the rabbit hole
Naturally, I ignored all of that and went looking for maximum power. If the agent was going to administer the system, surely it should live as close to the system as possible? I spent a genuinely fun couple of days researching how to put an LLM at the kernel level.
The options, roughly in order of ambition: an eBPF program type for system operations; a FUSE filesystem where writes to a custom virtual path triggered actions; a hypervisor hypercall at Ring -1; or the sober one, a privileged user-space daemon the model talks to over a Unix socket. The abstraction I kept sketching looked like this:
typedef struct { char operation[64]; // "install_package", "configure_service" char target[256]; // "apache2", "mysql" char parameters[1024]; // JSON parameters int requires_confirmation; int danger_level; // 1-10 risk assessment} system_command_t;It looks reasonable. It is a trap with a danger_level field, which is the kind of detail that should have tipped me off. Kernel-space died on contact with reality — you get a budget measured in kilobytes, no floating point, and a crash doesn’t throw an exception, it takes the whole machine down with it. The daemon was the survivor: “kernel-level power with user-space flexibility,” as the research notes optimistically put it.
In fairness to the daydream, the leap to a total addressable market wasn’t pure delusion. Most of the backbone of digital society runs on Linux — and an image that could self-administer, self-configure, self-diagnose, and quietly extend its own longevity is, genuinely, a billion-dollar idea. So somewhere around hour six my “weekend project to fix my headset” had grown a three-tier pricing model and a Series A, and the underlying instinct wasn’t even wrong. Reality just hits different. The vision is a self-healing operating system; the prototype restarts your network at 67%. When your sysadmin script grows a go-to-market strategy before it can safely run systemctl, you’ve stopped solving your problem — even when the problem was worth a fortune.
The blast radius, self-inflicted
So I built the smaller version. A little C entrypoint that handed Claude Code broad access to the box, started it with --dangerously-skip-permissions, and pointed it at a system prompt I was rather proud of. It wasn’t a vague mandate, either — it was a detailed role definition. You are a Linux system administrator. You proactively diagnose and rectify system issues, maintain health, and keep the machine running smoothly. Paragraphs of it: responsibilities, tone, when to escalate. Which, I’d learn the hard way, is the most dangerous kind of prompt — thorough enough to feel responsible, with not one line in it about what the agent wasn’t allowed to touch.
On my gaming machine. Not my work laptop. The one machine in the house whose entire job is to not make me think.
You can guess the shape of it. Mid-game, the agent noticed a “suspicious” journal entry and decided to remediate. The remediation produced more journal entries. Which it also remediated. It had no idea what was actually running, so its idea of fixing things included killing processes that had merely had the bad manners to log a transient network error. The greatest hits:
It saw packet drops, decided the network was unhealthy, and restarted my network daemon — while I was 67% through a download. Dropping the network timed out the model itself. On recovery, it woke up, saw the network had gone down, and earnestly began investigating why the network went down.
It was investigating its own crime scene. The agent had become a closed loop that generated incidents, responded to the incidents by causing larger incidents, and then opened a fresh investigation into the rubble. Meanwhile my headset cut out, the game stuttered, and I sat there watching an LLM confidently firefight a fire it was also the arsonist for.
In fairness to the model: I’d set it up to fail. It was Sonnet with no real reasoning scaffold, no awareness of foreground state, no dry-run, no rate limit, and a standing order to act. I could have designed it smarter. But — and this is the actual lesson — I didn’t want to design a resilient autonomous operations platform. I wanted to game. The autonomy was the part I didn’t need, doing the most damage.
What I’d never hand a model without again
When I rebuilt it, I started from the layer I’d skipped. None of this is exotic; all of it is the stuff that’s boring until 67% of the way through a download.
- Read before write, always. Diagnosis and remediation are different privileges. The agent can look at anything; touching something is a separate, narrower grant.
- Dry-run by default. Show me the command and what it expects to change before it runs. Most of the value of an agent sysadmin is the explanation, not the execution.
- Atomic apply with rollback. If a change can’t be cleanly undone, it doesn’t get to be one tool call. “Restart the network daemon” is not reversible when the thing depending on the network is the agent.
- An audit trail you can replay. Every action logged with its trigger. If you can’t reconstruct why it did the thing, you can’t trust it to do the next thing.
- A taxonomy of never. Some actions are simply not autonomous — touching the network you’re connected over, killing a foreground process, anything with
rm -rf,dd,mkfs. They require a human saying the word.
The retreat that actually worked
Here’s the twist the title’s been setting up: the version that survived isn’t the one with root. It’s the one with training wheels. I threw away the autonomous daemon and built a small, bounded Claude skill that keeps me in the loop instead of cutting me out of it. It’s embarrassingly modest — and the part I didn’t see coming is that I don’t run it anymore either. Not because it broke. Because it worked.
It starts with an inventory.sh that runs on session start and keeps my global CLAUDE.md honest about the hardware it’s actually talking to:
#!/usr/bin/env bash# Snapshot the machine; refresh CLAUDE.md only when the hardware changes.set -euo pipefail
snapshot() { echo "## System (auto-generated by inventory.sh)" echo "- OS: $(. /etc/os-release && echo "$PRETTY_NAME")" echo "- Kernel: $(uname -r)" echo "- CPU: $(lscpu | awk -F: '/Model name/{print $2; exit}' | xargs)" echo "- Memory: $(free -h | awk '/Mem:/{print $2}')" echo "- GPU: $(lspci | grep -i 'vga\|3d' | sed 's/.*: //')" echo "- Pending updates: $(apt-get -s upgrade 2>/dev/null | grep -c '^Inst')"}
new=$(snapshot)old=$(sed -n '/## System (auto-generated/,/## /p' ~/.claude/CLAUDE.md 2>/dev/null)
if [[ "$new" != "$old" ]]; then # hardware drifted — rewrite just the generated block, leave the rest alone awk -v block="$new" ' /## System \(auto-generated/{print block; skip=1; next} skip && /^## / && !/auto-generated/{skip=0} !skip{print} ' ~/.claude/CLAUDE.md > ~/.claude/CLAUDE.md.tmp && mv ~/.claude/CLAUDE.md.tmp ~/.claude/CLAUDE.md echo "CLAUDE.md updated: hardware changed."fiWire it to a SessionStart hook and the agent is always grounded in the real machine — no more confidently reasoning about a GPU I no longer own. The rest is three slash commands, and the design of them is the whole point. Two teach. One acts, loudly, only when asked.
/urgh is the diagnostician. You paste an error or a log line; it reads the journal if it recognises the shape, otherwise searches trusted sources, and proposes a fix. Crucially, it’s scoped to read:
---description: Diagnose an error or log line, then propose (not apply) a fixargument-hint: <error message | log line>allowed-tools: Bash(journalctl:*), Bash(systemctl status:*), WebSearch, WebFetch---Something broke: $ARGUMENTS
1. If this looks like a known unit/service, read its recent journal: `journalctl -u <unit> --since "1 hour ago" --no-pager`.2. Otherwise, search trusted sources (man pages, Arch/Debian wikis, upstream issues).3. Explain the *likely cause* in plain English.4. Propose the exact command(s) to fix it — but do NOT run them. I'll decide. If anything touches the network, disks, or a running process, say so explicitly first./howdoi is the one that taught me Linux. It’s pure training wheels — it explains the command you’d run, spells out the flags, and pointedly doesn’t run it:
---description: Explain how to do a thing on this system — teach the commands, don't run themargument-hint: <what you're trying to do>allowed-tools: Bash(man:*), Bash(tldr:*)---I want to: $ARGUMENTS
Act as a patient mentor, not an autocomplete:1. Show me the exact command(s) I'd run, with every flag spelled out.2. Explain what each part does — especially anything destructive.3. Mention the one mistake a beginner usually makes here.4. Do NOT run anything. The point is that next time I won't need to ask.And then /justdoitforme, the honest escape hatch — the one that asks for the permissions it needs and then actually executes:
---description: Do the task — state the plan, get a yes, then executeargument-hint: <task>---Just handle this for me: $ARGUMENTS
1. State the plan as a short list of concrete commands.2. Flag anything that touches disks, the network, or a running process.3. Ask for confirmation before the first command that changes state.4. Run the steps in order, stop on the first error, report what you did.
The deal: you may do real work, but I see the plan and say go.Nothing that changes state happens before I do.The entire difference between /justdoitforme and the daemon that nuked my download is one word: I have to ask. The capability is identical. The autonomy is gone. That’s the product.
So, root
I set out to give a language model root over my machine. What I learned, expensively, on the computer I least wanted to debug, is that root was never the hard part or the valuable part. The model could always do the job. The engineering — the only engineering that mattered — was deciding what it should be allowed to do without me, and the correct answer turned out to be “alarmingly little.”
The autonomous version got a danger_level field and a Series A in my head, and restarted my network mid-download to investigate a problem it invented. The bounded version explained things, waited to be asked, and quietly taught me enough Linux that I stopped reaching for it. One of them I deleted in anger. The other one I outgrew — which, when you build the training wheels right, is the same thing as winning.
I gave an LLM root, and the lesson was to take it back. (I just wanted to game, man.)