wait returns 0 in this case because it “remembers” that process 72009 was one of its children, and it remembers its children’s exit codes, and that exit code was 0. (The documentation is somewhat misleading here since it mentions “active” processes explicitly.)
Behind the scenes,
wait determines whether a given process identifier corresponds to one of the shell’s children, perhaps in a job; if so, it checks whether the process is still running. If it’s still running, it waits for it to finish. Once it’s finished, it determines the corresponding exit code (which could be for the process only, or for the job as a whole), and returns that. There’s a lot of additional complexity to deal with signals correctly, process substitution, controlling terminals etc. but that’s not relevant here.
The exit code is remembered (at least) in the job table. You can see this in action by running two commands with different exit codes (
false & and
true &), and waiting for the respective process identifiers. As long as the job table isn’t cleared,
wait will give the correct exit code. Run
wait with no argument to remove finished jobs from the job table, and you’ll see that you can no longer retrieve the exit codes of the earlier jobs.