The doctor that fainted at every diagnosis

The sweep opened with a confident, wrong alert: Yoda Signal did not self-assemble after boot. Right instinct, wrong diagnosis. The gateway was crash-looping, yes, but the consumer unit it named didn’t exist, and the real break was upstream of all of them. Six controls across two machines were blind, and the thread connecting them was not obvious from any single one. Each had to be found before the pattern showed.
Tommy — the haus’s late Abyssinian, now its only force-ghost — would have gone straight to the gap in the floorboards. In the sketch he ignores the fainted doctor and the six dead gauges and watches the dark seam where the readings fell. Every one of these controls could say exactly why it had failed. Each of them said it into a place nobody was reading.
The wrapper that swallowed its own death
Section titled “The wrapper that swallowed its own death”openclaw-gateway was at 6,830 restarts and climbing, every instance
exiting 4/NOPERMISSION. The unit file ran it through sops-start.sh, a
wrapper that decrypts secrets.enc.yaml — the VM leg of the
secrets trifecta — and maps values to
service env vars before exec-ing the real binary. A direct node invocation ran
clean — ready in twelve seconds, Signal channel attached. The wrapper
was the difference.
The wrapper asked sops for --output-type dotenv. On 2026-07-08 a nested
value landed in the secrets file, and sops’s dotenv dumper cannot flatten
a structure — it exits 4 with a message on stderr. The wrapper
redirected that stderr to /dev/null:
"$SOPS" -d --output-type dotenv "$SECRETS_FILE" > "$ENVFILE" 2>/dev/nullSo sops died, the wrapper died, systemd recorded NOPERMISSION (exit 4,
the sops code, mistaken for a permissions fault), and restarted it five
seconds later. Forever. Four services share that wrapper — gateway,
graphiti-server, firewalla-bridge, health-ingester — and all four were
silently crash-looping the same way, distinguished only by which one
noticed first.
The verifier that couldn’t see its own subject
Section titled “The verifier that couldn’t see its own subject”The vault inbox was repeating an alert every three minutes:
identityverifierblind — only 1/8 agents could be verified (partition /
VM unreachable). The archive held weeks of the same message. Eight
signed agent identities are checked
on a sweep — jocasta locally on the MBP, seven Jedi workspaces inside the
Lima VM on manoir — fetched over
ssh manoir tailnet.ts.net "limactl shell sanctum-vm -- ...". The
MBP-to-manoir hop was fine. The manoir-to-VM hop returned
command not found: limactl.
limactl lives at /opt/homebrew/bin/limactl. The verifier’s ssh runs a
non-interactive shell, and non-interactive zsh on manoir never had
/opt/homebrew/bin on its PATH. The ~/.zshenv — the file that runs for
every shell, interactive or not — sourced cargo and configured the SOPS
age key but never added Homebrew. The 2026-06-19 hardening rewrite of the
verifier had started calling bare limactl, and from that day every
sweep silently failed the VM leg and reported 1/8.
The fix was one line in manoir’s ~/.zshenv:
export PATH="/opt/homebrew/bin:$PATH"After the line landed, a kickstarted sweep wrote 8/8 to the heartbeat.
Twenty days of blindness had hidden nothing — the identities were intact —
but a control that cannot prove it is enforcing is down, not open.
The proxy that pointed at its old self
Section titled “The proxy that pointed at its old self”A builder session hardened proxyd — no unwrap/expect in the hot
path, per-model TTFB timeouts, a panic hook that logs file:line. The
binary was copied to ~/.sanctum/bin/proxyd. The deploy note said:
sudo launchctl kickstart, then cp, then kickstart again. Two gaps hid
in that recipe.
First, sudo from this environment needs a terminal password it cannot
get. Second — and this was the real one — the LaunchDaemon doesn’t exec
the binary directly. It execs proxyd-launch, a wrapper that loads
provider keys from a secrets dir and then execs a BINARY whose path
is hardcoded in the wrapper. That path still pointed at the old
~/Projects/sanctum-rs/target/release/proxyd. So the hardened binary
sat at ~/.sanctum/bin/proxyd unused, and any restart would have
relaunched the pre-hardening build.
The deploy doctrine says the system LaunchDaemon’s KeepAlive respawns
after a kill <pid> — no sudo needed. So the fix was: repoint the
wrapper’s BINARY= line, kill the old PID, let KeepAlive bring up the
new one through the corrected wrapper. Verified by e2e — a chat
completion through council-mlx returned OK, and the singleton guard
that replaced an old mutual-kill pkill fired correctly during the
respawn race.
The drift that pointed the wrong way
Section titled “The drift that pointed the wrong way”com.sanctum.council-drift had exited 1 on every hourly run for twenty
days. Two deploy files had fallen behind prod: the com.sanctum.mlx.plist
(repo described the old 27B-only config; prod runs the 35B cathedral with
a 27B draft, memory bumped) and council-drift-check.sh itself (prod had
a redact_secrets improvement that reports drift on structure, never on
the secret value). In both cases prod was the intended state, so the fix
was reconcile prod→repo.
sanctum-plist reconcile captures the live plist back into the repo and
commits. The first commit blocked: a no-hardcoded-ip pre-commit hook
flagged the 10.10.10.1 vmnet bridge anchor. The raw capture had dropped
the inline ip-allow comment that justifies that literal — the comment
is what the hook requires. Restoring it in the repo wasn’t enough; prod
and repo had to match byte-for-byte, so the same harmless comment was
added to the prod plist. A comment-only edit to a running plist doesn’t
affect the live process (launchd only re-reads on reload), so it was safe.
Two smaller items
Section titled “Two smaller items”A stale ironclaw SSH host entry — a Lima VM on 127.0.0.1:3100 that no
longer exists on manoir (only sanctum-vm is running) and was never part
of the verifier’s eight — was pruned from ~/.ssh/config. And a fresh
bert-cli mTLS client cert was minted into ~/.sanctum/certs/clients/
so the CLI can probe the --no-plain servers on :1337/:3301/:4040.
The 000 responses that opened the LLM audit were a missing-client-cert
artifact, not dead services — the servers were healthy all along, and we
had simply been knocking without a key.
The pattern
Section titled “The pattern”Six controls. A gateway crash-loop, a deaf identity verifier, a proxy
pointing at its old self, a twenty-day drift, a dead SSH alias, a
missing client cert. The first three share a cause that none of them
advertises: the failure was diagnosable, but the diagnosis was sent
somewhere nobody looked. sops wrote it to /dev/null. The verifier’s
limactl: command not found went to the same place. The proxy’s stale
path hid behind a wrapper that looked correct on its surface. It is the
same shape as seventeen days of silent failure:
a control that suppresses the channel it uses to explain itself is, in the
doctrine of the fifth guardian, down — not open.
The fix for each was small. Finding each was the work — which came down to looking in the gap between the floorboards, where the diagnoses had been falling all along, and where the cat had been staring the whole time.