Asking for more rows than fit in a 16 MiB frame, and asking for so many that the server was
OOM-killed, produced the same thing at the client both times: a bare
EOF. The benchmark rig can only tell the two apart by redialling — if the dial
succeeds the server merely hung up, if it is refused the process is gone. Every client would have
to do the same, and a client that does not do it reports a crash where there was a refusal.
The cap is enforced in protocol.Encode, which returns
ErrPayloadTooLarge. The server's send path passes that error straight up:
func write(conn io.Writer, frame protocol.Frame, payload []byte) error {
frame.Version = protocol.Version
frame.Payload = payload
encoded, err := protocol.Encode(frame)
if err != nil {
return err
}
There is nowhere left to put the refusal. The one channel for telling a client anything is a frame, and the failure being reported is that a frame could not be made. So the connection closes, and closing a connection is the one signal that carries no information.
This matters more than it looks, because this project already decided that a refusal is not prose.
failure() in the server carries a message and a code, with the reason written
down next to it: a client that only receives prose cannot act on it, and telling refusals apart by
matching strings is how a driver breaks when a server improves its wording.
ISS-21 exists because one refusal reached a caller as "failed".
This is the same principle failing at the one point where the mechanism for codes cannot be used.
ErrRefused is returned as *wire.ErrRefused. Worth looking at while
this path is open, because a client matching on a concrete pointer type is the same fragility one
layer down.
Measured 2026-09-21 on 737e149, 2 KB rows, plaintext TCP, one connection. The
behaviour is in the code path above and does not depend on the container limits — the benchmark is
how it was noticed, not what makes it true.