#1493·lefthook

`lefthook run post-checkout` blocks until stdin EOF, deadlocking when the caller holds stdin open

Author: evanharmon1Created Aug 16, 2026Updated Aug 16, 2026

Summary

On v2.1.10, lefthook run post-checkout does not run its jobs (or exit) until its stdin reaches EOF. When the invoking context holds stdin open without writing — an automation harness with a socket stdin, a task runner pipe, a CI wrapper — the hook deadlocks indefinitely: banner printed, no child processes, main thread parked (futex_do_wait on Linux, __psynch_cvwait on macOS).

Git hands post-checkout no stdin payload (its inputs are the three arguments), so blocking on stdin here has no upside; the payload-carrying hooks are the ones that legitimately read stdin. Of the hooks we tested, only post-checkout exhibits thispre-commit and pre-push return promptly under the same conditions.

Where this bites in practice: any scripted git worktree add / checkout automation whose hooks run with an inherited, long-lived stdin. Because TTYs and /dev/null EOF immediately while pipes and sockets never do, the hang looks intermittent across invocation contexts and is easy to misdiagnose as a race.

Minimal reproduction

bash
d=$(mktemp -d)/r && mkdir -p "$d" && cd "$d" && git init -q .
git config user.email [email protected] && git config user.name a
cat > lefthook.yml <<'EOF'
post-checkout:
  commands:
    noop:
      run: "true"
EOF
git add -A && LEFTHOOK=0 git commit -qm init && lefthook install -f

# completes in ~0.06s:
timeout 10 lefthook run post-checkout </dev/null; echo "eof-stdin exit=$?"

# hangs until killed (exit 124), job never runs:
mkfifo pipe; sleep 60 > pipe &
timeout 10 lefthook run post-checkout < pipe; echo "open-stdin exit=$?"

Same behavior with the identical script comparing hooks: pre-commit and pre-push exit 0 under the open-pipe stdin; post-checkout exits 124 (killed), deterministically.

Environment

  • lefthook v2.1.10 (latest at time of filing), reproduced on:
    • macOS 15.x, Homebrew install, Apple Silicon
    • Debian-based Linux container, arm64, release binary
  • Observed originally as intermittent CI/test-suite deadlocks; the repro above is deterministic once stdin is controlled.

Expected

post-checkout jobs run without waiting on stdin, since git supplies that hook no stdin data — or at minimum, stdin consumption is opt-in the way payload-carrying hooks need it.

Possibly related history: #588 (stdin swallowed from the terminal), #733 (request for an option to redirect stdin to /dev/null), #836 (pre-push stdin hang with LFS).

Downstream diagnosis with additional detail: https://github.com/evanharmon1/harmon-init/pull/904