It seems that only Popen::poll() sets Popen::retcode_ (which can be retrieved using Popen::retcode()), but Popen::communicate() instead calls Popen::wait() and throws away the result. I tried calling Popen::poll() myself, but it seems that just returns 0 (IIRC waitpid will only work once).
A simple fix seems to be to let communicate() do retcode_ = wait();, which works, but I'm not sure if that's the best approach here (perhaps wait() should just set it?).