Skip to content

Windows: the commit-msg hook hangs instead of returning, and #71's containment can never match there #321

Description

@MongLong0214

Recorded from T-1124 (#283), which set out to establish #71's install-root containment for the wrapper path on Windows. The measurement answered a different question than the one it was written to ask, so it is recorded here rather than absorbed into that ticket.

All of the below is from a windows-latest runner. Nothing here is inferred from macOS behaviour.

1. The commit-msg hook never returns on Windows

A real git commit in a repository where commitlore init installed the hook does not fail and does not succeed. It hangs. In CI it was killed at a 90s bound; the job's cleanup then reported Terminate orphan process: pid (7764) (sh), so the shell was still spinning after the commit was killed.

The cause is the last resolution step in the stub (src/hooks/commit-msg.ts):

dir=$PWD
while [ -n "$dir" ]; do
  if [ -x "$dir/node_modules/.bin/commitlore" ]; then exec ...; fi
  dir=${dir%/*}
done

${dir%/*} returns its input unchanged once no / remains. A probe hook installed in a scratch repository on the same runner reports what $PWD actually is inside a Git for Windows hook:

hook argv0    = .git/hooks/commit-msg
hook PWD      = C:/Users/RUNNER~1/AppData/Local/Temp/.../probe
hook pwd      = C:/Users/RUNNER~1/AppData/Local/Temp/.../probe
hook pwd -P   = /c/Users/runneradmin/AppData/Local/Temp/.../probe
hook uname    = MINGW64_NT-10.0-26100
walk: NON-TERMINATING, stuck at [C:]

The walk was executed with a counter on the runner. It does not terminate; it settles on C: and stays there. For a user this is worse than either accepting or refusing: the commit never comes back.

2. #71's install-root containment cannot match on Windows

The guard the walk is reached through:

recorded_dir=$(cd "$(dirname "$recorded")" 2>/dev/null && pwd -P) || recorded_dir=
case "$recorded_dir" in
  "$recorded_root"|"$recorded_root"/*) exec "$recorded_node" "$recorded" ... ;;
esac

Measured on the runner:

commitlore.bin  = C:\Users\...\commitlore\v9.9.9\dist\commitlore.mjs
commitlore.root = C:\Users\...\commitlore\v9.9.9
dirname+pwd -P  = /c/Users/.../commitlore/v9.9.9/dist
containment case: NO MATCH -- the stub falls through
test -x node: yes
test -L bin: no

commitlore.root is written by recordBinPath as realpathSync(PACKAGE_ROOT), which on Windows is a win32 path. The stub reads it under Git for Windows' sh, where pwd -P answers in POSIX form. Both of the guard's other conditions pass; only the comparison fails, and it fails for every value — the installer's own bundle included. The subdirectory alternative is also hardcoded to /, which a win32 recorded_root never contains.

So the containment check is not holding on Windows and it is not permitting on Windows. It is a dead branch, and it is the reason control reaches the walk in §1 at all.

Note the same section 1 probe shows $PWD and pwd -P disagreeing inside a single hook invocation — the two path worlds are both present, which is what makes a single-form comparison unsafe here.

3. doctor reports no problem in exactly this state

readRecordedHookTarget in src/core/hook-target.ts mirrors the stub for doctor, using node:path:

const isInsidePackage = (path: string): boolean => {
  const fromRoot = relative(realpathSync(PACKAGE_ROOT), realpathSync(path));
  return fromRoot !== '..' && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot);
};

On Windows node:path is path.win32, so it handles the backslashed pair correctly and returns dist\commitlore.mjs — contained, no problem pushed. The mirror is green precisely when the hook it claims to mirror is dead. Any fix has to close this divergence too, or the check that is supposed to warn about a broken hook keeps reporting it as healthy.

4. The PATH fallback cannot find the installed shim

command -v commitlore: NOT found (the shim is commitlore.cmd)

install.ps1 installs commitlore.cmd. MSYS sh's command search does not append .cmd, so the fall-through cannot recover here either. This is also why the PreToolUse hook runtime check reports configured PreToolUse hook executable "commitlore" is not resolvable from PATH.

What this does not say

doctor's commit-msg hook and hook runtime checks report could not run the hook: spawnSync /bin/sh ENOENT. That is the probe's own failure and not the hook's: the runner shows git using /usr/bin/sh and the hook demonstrably runs, since it hangs. Those two doctor checks cannot report on a Windows hook at all until they stop spawning /bin/sh directly. Separating the probe's failure from the hook's was the first thing T-1124 had to measure, and this is the result.

Consequence for the Windows support claim

Windows is reachable through install.ps1 (T-1121, #282). It is not supported, and no document should say otherwise. #283's baseline — a valid record accepted and an invalid one refused, through the recorded install — fails on windows-latest, so #71's two containment attacks behind it have not executed and nothing has been established about them either way.

Evidence: PR #320's install-ps1 job. git version 2.55.0.windows.2, /usr/bin/sh, MINGW64_NT-10.0-26100, Node 22.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    epic:F14Distribution and install UX: plugin-first, Node-only scripts

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions