A verification script hung all night on one stuck network call. The timeout 30 guard in front of it never ran because stock macOS has no timeout(1). It is a GNU coreutils tool; Homebrew installs it as gtimeout. A script that must run on a clean machine cannot assume either one is present.
perl ships with macOS, and its alarm covers the gap:
perl -e 'alarm shift; exec @ARGV' 30 some-command --with args
alarm shift arms a 30 second SIGALRM. exec then replaces perl with the command, same process, so the pending alarm rides along. Finish first and the timer dies with the process. Hang, and SIGALRM terminates the command at the deadline. No polling loop, no PID bookkeeping, no dependency.
Detecting a stuck process well is a harder problem; a hard deadline is the blunt version you can ship today.