From bd8d29144f9e8f86c79f7bde311dd3b854ef41a7 Mon Sep 17 00:00:00 2001 From: Fabian Ising Date: Mon, 13 Jul 2026 15:53:50 +0200 Subject: [PATCH] [ZSH] Bound background git calls to prevent zombie leaks The check_tools_repo (git ls-remote | awk) and update_dotfiles (git fetch) background jobs could stall on a network call and, being disowned (&!), orphan into days-long processes holding unreaped git/awk children. Bound both network probes with `timeout` (guarded, absent on macOS) and disable ssh multiplexing so no daemonized mux master inherits our fds. Also dedup the double update_dotfiles startup call. Co-Authored-By: Claude Opus 4.8 --- zsh/.zshrc | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/zsh/.zshrc b/zsh/.zshrc index 2a0a053..33a6b6f 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -31,7 +31,13 @@ fi # Per-branch, so `server` on each device fast-forwards to origin/server. update_dotfiles() { local repo="$HOME/dotfiles" - git -C "$repo" fetch --quiet 2>/dev/null || return + # Bound the network call so a stalled fetch can't wedge this backgrounded + # (&!) job into a days-long orphan holding unreaped children. `timeout` is + # absent on macOS, so only use it when present. Disable ssh multiplexing so + # no daemonized mux master can inherit and keep our fds open past git's exit. + local -a TO; (( $+commands[timeout] )) && TO=(timeout -k 5 15) + GIT_SSH_COMMAND='ssh -o BatchMode=yes -o ConnectTimeout=5 -o ControlMaster=no -o ControlPath=none' \ + $TO git -C "$repo" fetch --quiet /dev/null || return git -C "$repo" rev-parse --abbrev-ref '@{u}' >/dev/null 2>&1 || return # no upstream git -C "$repo" merge-base --is-ancestor '@{u}' HEAD 2>/dev/null && return # already current git -C "$repo" merge --ff-only --quiet '@{u}' 2>/dev/null \ @@ -41,7 +47,6 @@ update_dotfiles() { update_dotfiles &! if [ "$shared_config" -eq 0 ]; then - update_dotfiles 2>&1 &! export GIT_AUTHOR_NAME="Fabian Ising" export GIT_AUTHOR_EMAIL="f.ising@fh-muenster.de" export GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME @@ -58,13 +63,17 @@ fi # Uses the first candidate that is a git checkout; covers global and per-user layouts. check_tools_repo() { local repo branch remote mergeref remote_sha local_sha cmd + # See update_dotfiles: bound the network probe (timeout, absent on macOS) and + # disable ssh multiplexing so a stalled ls-remote can't orphan this &! job. + local -a TO; (( $+commands[timeout] )) && TO=(timeout -k 5 10) for repo in /opt/tools "$HOME/tools" "$HOME/tools/tools-repo"; do local -a g=(git -c 'safe.directory=*' -C "$repo") $g rev-parse --is-inside-work-tree >/dev/null 2>&1 || continue branch=$($g symbolic-ref --short HEAD 2>/dev/null) || return remote=$($g config "branch.$branch.remote" 2>/dev/null) || return # no upstream mergeref=$($g config "branch.$branch.merge" 2>/dev/null) || return - remote_sha=$($g ls-remote "$remote" "$mergeref" 2>/dev/null | awk '{print $1}') + remote_sha=$(GIT_SSH_COMMAND='ssh -o BatchMode=yes -o ConnectTimeout=5 -o ControlMaster=no -o ControlPath=none' \ + $TO $g ls-remote "$remote" "$mergeref" /dev/null | awk '{print $1}') local_sha=$($g rev-parse HEAD 2>/dev/null) [[ -n "$remote_sha" && -n "$local_sha" ]] || return # remote unreachable [[ "$remote_sha" == "$local_sha" ]] && return # already current