I may be wrong about the nature of the issue, or I may simply be missing something stupid. Here’s a simplified version of the code in question:
let do_request sock ic oc msg =
let%lwt () = Lwt_unix.wait_write sock in
let%lwt () = Message.write oc msg in
let%lwt () = Lwt_unix.wait_read sock in
let%lwt resp = Message.read ic in
Lwt.return resp
From the REPL, it works as expected, even though it takes a lot more time than I’d expect (on the order of seconds). In the compiled binary, however, it exits with 0 right at the Lwt_unix.wait_read.
On the server side I see that the request message is received by the server, but then it tries to send a reply, the client program has already exited and it gets a SIGPIPE.
Any suggestions how to debug this?

