When true exits, the read side of the pipe is closed, but yes continues trying to write to the write side. This condition is called a "broken pipe", and it causes the kernel to send a SIGPIPE signal to yes. Since yes does nothing special about this signal, it will be killed. If it ignored the signal, its write call would fail with error code EPIPE. Programs that do that have to be prepared to notice EPIPE and stop writing, or they will go into an infinite loop.
If you do strace yes | true1 you can see the kernel preparing for both possibilities:
write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++
strace is watching events via the debugger API, which first tells it about the system call returning with an error, and then about the signal. From yes's perspective, though, the signal happens first. (Technically, the signal is delivered after the kernel returns control to user space, but before any more machine instructions are executed, so the write "wrapper" function in the C library does not get a chance to set errno and return to the application.)
1 Sadly, strace is Linux-specific. Most modern Unixes have some command that does something similar, but it often has a different name, it probably doesn't decode syscall arguments as thoroughly, and sometimes it only works for root.
yes | tee >(true) >/dev/nullwill do as you expect, btw, asteecontinues until all writers are dead, sotrueexiting won't disrupt it entirely.trueis basically a{return 0;}program, so I wouldn't expect it to run for long, let alone forever.