Looking at the logs, it turns out the hangups were caused by some old code of mine from an earlier versions of a script that must have been executed after the current script.
I traced the code to the certificate-specific configuration file inside /etc/letsencrypt/renewal. The config contained entries for all three renewal hooks with values corresponding to an old set of scripts.
After manually removing the renewal hook entries in the config file, they haven't appeared again, and I haven't been able to reproduce the issue.
I'm wondering if there's any information available on:
How the hooks might have ended up inside the config file.
How Certbot handles CLI option hooks, hook files, and hook entries in the certificate-specific config files when all are present.
I've checked the .conf file after another dry run with a --deploy-hook value, but it doesn't have any hook entires. Does it need to be a live renewal to update the .conf?
What's still interesting to me is that Certbot appears to execute the code found in the .conf file subsequent to the code in /etc/letsencrypt/renewal-hooks, at least on dry runs, while it overrides the .config file code with any CLI options: