Luther Monson, Staff Software Engineer at SUSE

Luther Monson

Go by way of PHP · Containers · VMs · Windows · Kubernetes

Your Agent's Shell Allowlist Is Bypassable

A permission gate for AI agents' shell commands judged spellings instead of commands. I found several classes of bypass in shipmates' own gate, and one idea fixes most of them.

9-Minute Read

A shell command decomposed into the nested command lines the permission gate has to judge

Every shipmates crew member runs shell commands. Before any of them runs, it goes through a permission gate. The gate uses the same rule syntax as Claude Code settings: Bash(git status), Bash(rm *), Write(**/.claude/settings.json). The rules are merged from several layers:

Cross-Platform Process Containment: Job Objects vs Process Groups vs cgroups

How shipmates bounds and tears down agent process trees on Windows, Linux and macOS. It also covers why I deleted 1,206 lines of cgroup code and the grandchild that escaped the memory cap everywhere except my dev box.

8-Minute Read

Agent process tree with the scope of kill and measurement on Windows, Linux and macOS

Every turn a shipmates crew member takes is a subprocess. It’s claude or the codex app-server. That in turn spawns shells, go test, npm or whatever the task calls for. Two things have to be true about that tree. When a turn ends or gets killed, the whole tree goes, including the grandchildren. And if somebody sets a memory or CPU budget, the budget applies to the whole tree, not just the process I happened to start.

A 1 TB Test: Building a Memory Watchdog for go test

A runaway test binary hit about 1 TB of commit charge and took my desktop down. Here's the PowerShell and POSIX sh wrapper I put around go test, and the bug in it that the Windows box couldn't show me.

8-Minute Read

go test process tree with per-package test binaries watched by the safe-test wrapper

On August 16th a server.test.exe from shipmates climbed to roughly 1 TB of commit charge on my Windows workstation. Windows Terminal was hosting several agent sessions in tabs. It hard-faulted with E_OUTOFMEMORY, and every one of those sessions died at the same moment. It wasn’t the first time either. An earlier one hit about 236 GB through an infinite filepath.Dir loop.

os/exec's StdoutPipe Footgun: How cmd.Wait() Ate My Last Frame (Twice)

Calling Wait while a StdoutPipe reader is still draining throws away the child's last output. I shipped this bug twice in shipmates.

9-Minute Read

Timeline of a child process exiting, Wait closing the pipe, and the reader losing buffered frames

shipmates spends most of its life spawning claude (and codex) processes and reading newline-delimited JSON off their stdout. The last frame of a turn is the important one. It’s a result frame with the cost, the model and the final text. Stderr matters too, because that’s where Claude Code tells you Invalid API key · Please run /login. For a while I was throwing both away under load. The cause is documented right there in the os/exec godoc. I fixed it in one package and then found the…

shipmates Short Ones: 8 Minutes Extracting Go, and Three Voice-Loop Bugs

Four small stories from shipmates, starting with setup-go stuck extracting on Windows CI and Cloudflare's 100 second timeout. The other two are a voice loop that transcribed itself and events lost to a timestamp watermark.

5-Minute Read

setup-go on Windows CI: 7 seconds to download, 473 seconds extracting before the timeout

A few shipmates bugs don’t need a whole post each. They were too good to leave in commit messages, though.

Finding a Data Race in rancher/remotedialer, Then Pinning Only Two-Thirds of the Fix

Two data races in remotedialer v0.6.1 that upstream's tests never ran with -race, and how my replace directive ended up pinned to the middle commit of my own fix.

7-Minute Read

Three commits of remotedialer PR 265 and which one the replace directive pointed at

shipmates ships connect to Fleet Command over outbound websocket tunnels. That way nothing on the ship side has to be exposed to the internet. I didn’t write that tunnel layer. I used rancher/remotedialer, the same library Rancher uses to reach downstream cluster agents. I spent years around that code at Rancher, so it was the obvious pick. What I didn’t expect was for my CI’s race job to find two data races in it in the first week.

shipmates: Persona-Based AI Coding Crews That Remember

shipmates is a Go CLI and daemon that runs crews of AI coding agents across machines. It adds per-project memory, a shared task graph, voice control and a real permission model.

7-Minute Read

shipmates hierarchy: Admiral, Fleet Command, ships, captains and mates

Since June most of my evenings have gone into shipmates, a Go CLI and daemon for running crews of AI coding agents. I tagged v0.5.0 on August 17th. That was two months and about 240 commits after the first architecture doc. So it’s a good time to write down what it is and why it exists.

Atomic File Replacement on NTFS Is Not rename + fsync

The Unix recipe for durable atomic file replacement doesn't translate to Windows. This is what shipmates uses instead, plus the delete-pending window that made an 8-way test fail only when the whole suite ran.

7-Minute Read

How long a losing concurrent replacer waits on NTFS, idle disk vs a disk under fsync load

shipmates writes a lot of small state files that other processes read. Voyage plans and state, tracker files. Each of them has to be replaced atomically, so a reader sees the old file or the new one and never half of each. Each also has to be replaced durably. When the function returns success, a power cut doesn’t take it back. Every Unix programmer knows the recipe for that. It doesn’t work on Windows, and the reasons are more interesting than “Windows is different”.

Recent Posts

Categories

About

Luther is a 20+ year software veteran specializing in backend APIs and architecting products. Employed at SUSE as the Team Lead for the Containers on Windows Team working on Rancher + Kubernetes