docs: a negative result is usually your own filter (skill + AGENTS.md)
Lint / hadolint (push) Successful in 13s
Lint / actionlint (push) Successful in 16s

Three false negatives in one session, all self-inflicted, all convincing
because the command "succeeded": a `| head -20` proved an SSH peer absent
that sits at line 454 of a ~500-line config; `ssh mac 'docker ps'` proved
the host had no Docker, when the non-interactive PATH simply lacks
/usr/local/bin; and `grep 'ssh '` proved no ControlMaster was running,
when those processes rename themselves to `ssh: <path> [mux]`. Same root
cause each time, so it goes in the skill rather than in a commit message:
a positive result carries its own evidence, absence has to be earned.

The skill (rootfs/, symlinked into ~/.agents/skills) is BAKED, so this is
an image change and is logged in CHANGELOG Unreleased accordingly. Its §3
also now records that a live ControlMaster socket makes later commands
authenticate not at all -- after editing a peer's authorized_keys, "it
still works" proves nothing; prove it with -o ControlPath=none, or the
breakage waits for a future session that has no memory of the edit.

AGENTS.md: corrected a stale CI claim while placing the pointer. It said a
tag push produces two runs including lint; lint.yml has since been scoped
to branches: ['**'], which excludes tag refs, and refs/tags/v1.8.4 duly
produced run 571 (publish) and nothing else. Kept the head_sha + workflow
path filter advice, which is cheap and guards against a future v*-triggered
workflow. Added a short section on verifying this repo from inside a
container, including that docker-compose.yml here is a TEMPLATE pinning
:latest while a real host runs its own per-machine file -- recreating from
the repo copy can silently move a host off :latest-studio.

Placement note: AGENTS.md is only auto-read when the cwd is this repo, so
the durable rule lives in the skill, which loads by description match in
any pi-devbox session.
This commit is contained in:
Joakim Persson
2026-08-22 22:56:41 +02:00
parent 2ebf00d6d4
commit fbc1f86612
3 changed files with 124 additions and 4 deletions
@@ -130,6 +130,36 @@ are "command not found" there — you must spell out the underlying command.
If a command "works in my terminal but not when the agent runs it," this alias
gap is the first thing to suspect.
### A negative result is usually your own filter
**When you are about to report that something is absent, unreachable, or not
running, the filter you wrote is the prime suspect — not the thing.** This
environment produces false negatives cheaply, and they are convincing because
the command "succeeded". Three real instances from one session, all wrong, all
mine:
| Claim I made | Why it was false |
|---|---|
| "`tor-ms22` is not in the SSH config" | `grep … \| head -20` — the entry was at **line 454**. `~/.ssh/config` here is ~500 lines. |
| "the Docker host has no `docker`" | non-interactive SSH `PATH` lacks `/usr/local/bin` (§2, §3). It was at `/usr/local/bin/docker`. |
| "no ControlMaster is running" | pattern `ssh ` (trailing space) cannot match a master: those processes **rename themselves** to `ssh: <controlpath> [mux]`. |
Habits that would have caught all three:
```sh
# don't cap the output of a search whose answer you don't already know
grep -n -i -A6 'tor-ms22' ~/.ssh/config # not | head -20
# on the host, resolve the binary instead of trusting PATH
ssh -F "$HOME/.ssh-local/config" mac 'command -v docker || ls /usr/local/bin/docker'
# match a process's ACTUAL argv, not the name you imagine
ps -eo pid,etime,args | grep -Ei 'mux|mosh|ssh'
```
A positive result needs no such scepticism — it carries its own evidence. Only
absence has to be *earned*, so spend the extra command there.
**`dscp`/`scp` with accented filenames on a macOS host.** macOS stores filenames
in Unicode **NFD** (decomposed — e.g. `ä` is `a` + combining U+0308), while the
string you type or paste is usually **NFC** (precomposed `ä`, U+00E4). The bytes
@@ -175,6 +205,23 @@ Two related mechanisms (don't reinvent them):
CGNAT per-destination flow caps on residential ISPs. If `~/.ssh/config` pins
a `ControlPath` under the read-only `~/.ssh`, override with
`-o ControlPath=none` (or use the sidecar, which already redirects it).
- **A live master socket MASKS auth and config changes on the far end.** Once
`~/.ssh-local/cm/<user>@<host>:22` exists, later commands ride it and
authenticate **not at all** — so after editing remote `authorized_keys`,
`sshd_config`, host keys, or firewall rules, "it still works" proves nothing.
A corrupted `authorized_keys` then bites on the next *cold* connect, likely in
a future session with no memory of the edit. Prove it immediately instead:
```sh
ssh -F "$HOME/.ssh-local/config" -O check <host> # 'Master running (pid=N)'
ssh -F "$HOME/.ssh-local/config" -o ControlPath=none -o ControlMaster=no \
-o BatchMode=yes <host> 'echo COLD AUTH OK'
```
To attribute a socket rather than guess whose it is: `ps -p <pid> -o
pid,ppid,lstart,etime,args`. A `mosh` the *user* started on the host
bootstraps with the **host's** `~/.ssh/cm/` and is invisible from in here;
only a mosh started *inside* the container shares `~/.ssh-local/cm/`.
- **`pi --ssh <host>`** rewires pi's own read/write/edit/bash tools to run on a
remote host; it has its own writable-socket fallback. See the `pi-extensions`
skill for that path.
@@ -257,6 +304,10 @@ hardcode. Details are in the `mempalace` skill.
- [ ] Writing durable output? → `/workspace`, not the ephemeral layer.
- [ ] Using `dssh`/`dscp`/`ll` in the bash tool? → spell out the real command.
- [ ] Assuming a hostname / domain / nameserver / host OS? → stop, detect it.
- [ ] About to report something **absent / unreachable / not running**? → re-run
without your own `head`/pattern/`PATH` assumptions first (§2).
- [ ] Changed remote `authorized_keys` / `sshd_config`? → prove it with a **cold**
connect; a live master socket hides breakage (§3).
- [ ] "Resolves but won't connect"? → check route *and* DNS (§3 + §4).
- [ ] `apt`/toolchain install? → tell the user it's ephemeral unless imaged.
- [ ] Editing a skill? → `readlink -f ~/.agents/skills/<name>` first (§1).