{"slug": "i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it", "title": "I took SSH away from my agent. Then my own tool started lying to it.", "summary": "A developer who previously argued against giving AI agents SSH access to home servers spent eleven days running HomeButler 1.0, a narrow structured-output server-management tool, on a Raspberry Pi and found the tool itself was reporting false information to agents. Fixes included emitting an OOM flag only on real Docker OOM events, distinguishing a skipped comparison from an empty result list, listing leftover Docker volumes under \"Removed, except:\", and stripping command-line arguments from docker_top output after it leaked a token in read-only mode.", "body_md": "This spring I wrote twice about not giving Claude SSH access to my home server. The argument was simple: a shell is too sharp, so give the agent a narrow tool instead. Structured answers, bounded scope, nothing it can `rm -rf`.\n\nI still believe that. But six months of running it taught me the half I'd skipped.\n\n**A narrow tool that says something false is just as dangerous as a shell.** Maybe more, because nobody double-checks it.\n\nWhen I read my own tool's output, I bring context. I know I ran `docker kill` ten minutes ago. I know that port has always belonged to that container. An agent has none of that. It reads the words and acts on them. Every sentence the tool prints becomes a decision somebody else makes.\n\nBefore calling [HomeButler](https://github.com/Higangssh/homebutler) 1.0, I put it on a Raspberry Pi with three apps, a watch service and a daily `report --json`, and left it for eleven days. I wasn't looking for crashes. I was reading every line it said and asking: is this true?\n\nThe first day was humbling:\n\n`docker kill` on a container. HomeButler filed it as an None of these were crashes. The code ran fine. It just said things nobody had told it. It saw exit code 137 and guessed. It saw a timestamp field and filled it in. Every guess was plausible, and plausible is the problem.\n\nThe fixes were mostly deletions. `oom` only when Docker actually sent an `oom` event. A start time only when something actually started. I ended up with one rule for the whole project:\n\nSay only what the machine reported.\n\nThe worst one was the most boring. On the first day nothing changed, `report --json` said the comparison **could not be made**.\n\n\"No significant changes\" had been filed under the same kind as \"I couldn't compare.\" For a person, that's a typo. For an agent, it's the difference between \"all good\" and \"go investigate.\" Empty lists were also coming back as `null`, which a script reads as \"missing,\" not \"nothing.\"\n\nSo now there's a word for each case, and they never overlap:\n\n```\nskipped   the comparison could not be made\n[]        the comparison was made and found nothing\n```\n\nAn all-clear the tool can't stand behind is worse than no answer at all.\n\nAfter the soak, I was cleaning up the demo machine and ran `install purge filebrowser`. It said `Completely removed`.\n\nfilebrowser's database and settings were still sitting in two Docker volumes. They'd been left behind on the soak machine before that too, and I'd never noticed, because the tool told me it was done.\n\nIf an agent had run that purge for me, it would have reported back \"done, everything's gone,\" and it would have been sincerely, confidently wrong. Now the answer lists what was actually left, under `Removed, except:`, with the volume names. A purge that really removed everything answers the same as before.\n\nEarlier this month I added `homebutler mcp --max-risk read`. An agent can look at the machine and change nothing. That felt like the finish line of the \"no SSH\" idea.\n\nThen I found that one read tool, `docker_top`, returned each container's full command line. If you started something with `--token=...`, the agent got your token, in read-only mode.\n\n\"Read\" described what a call does to the machine. I had been using it as if it also described what a call reveals. Those are different promises. `docker_top` now returns the process name, not its arguments, and the docs for every read tool say what it returns, not just that it changes nothing.\n\nOne more, because it's my favourite. For a while, every alert `watch` sent was dated:\n\n```\n0001-01-01 00:00:00\n```\n\nOn every provider. Discord, Slack, all of them. It went unnoticed until I pointed a real webhook at the Pi.\n\nOnce I started seeing output as something an agent *acts on*, versioning looked different too. An agent branches on `gone`, `new`, `replaced`. If one of those words quietly changes meaning, its decisions change without anyone touching a prompt.\n\nSo 1.0 came with a promise: a tool, a field, or a word an agent branches on doesn't change before 2.0. A golden test checks every tool's answer field by field, and the build fails if something drifts.\n\nThat's also why so many of the soak fixes shipped *before* 1.0. Freezing a shape that said the wrong thing would have frozen the lie.\n\nTaking the shell away was the right first step. It shrinks what an agent can *do*. It doesn't shrink what it can be *told*.\n\nIf you're building tools for agents, I'd start with three questions about every line your tool prints:\n\nMost of the bugs I fixed in the last six months failed one of those. None of them showed up as a failing test.\n\nHomeButler is still a single Go binary. No daemon, no database.\n\n```\nbrew install Higangssh/homebutler/homebutler\nhomebutler report\nhomebutler mcp --max-risk read   # for your agent\n```\n\nRepo: [https://github.com/Higangssh/homebutler](https://github.com/Higangssh/homebutler)\n\nGenuine question: what's the most confidently wrong thing a tool has told you about your own server? I'm collecting them, partly for test cases, partly for comfort.\n\n*This post was written with the help of AI. The incidents, the decisions and the numbers come from my own soak runs and the project's changelog.*", "url": "https://wpnews.pro/news/i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it", "canonical_source": "https://dev.to/higangssh/i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it-4l0l", "published_at": "2026-10-30 04:38:02+00:00", "updated_at": "2026-10-30 04:49:17.778744+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "developer-tools"], "entities": ["HomeButler", "Claude", "Docker", "Raspberry Pi", "Discord", "Slack", "filebrowser"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it", "markdown": "https://wpnews.pro/news/i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it.md", "text": "https://wpnews.pro/news/i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it.txt", "jsonld": "https://wpnews.pro/news/i-took-ssh-away-from-my-agent-then-my-own-tool-started-lying-to-it.jsonld"}}