From 7d5d4be32879ec903d574d95d03fd16516c57df3 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 | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/zsh/.zshrc b/zsh/.zshrc index ab643fa..e561c88 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -12,7 +12,13 @@ # 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 \ @@ -31,13 +37,17 @@ update_dotfiles &! # 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