fix(bin): resolve harness ancestry on Windows/Cygwin sessions - #1864
fix(bin): resolve harness ancestry on Windows/Cygwin sessions#1864podledges wants to merge 2 commits into
Conversation
Git for Windows bundles a Cygwin `ps` with no `-o` support at all, so every ps -o comm=/args=/ppid= call in the ancestry walk failed outright and fm-lock.sh reported "cannot locate harness process in ancestry" on every Windows session. Read /proc as a same-host fallback when ps -o produces nothing (Cygwin and MSYS2 both expose it; native Linux and macOS do not, so this path is a no-op there). Even with that fixed, Cygwin/MSYS2 report a process's parent as the synthetic orphan pid 1 the moment the real parent was never spawned through their own pid database - true for every bash.exe a native claude.exe launches, since each Bash-tool call is a fresh, otherwise unrelated subprocess. Bridge that gap by continuing the walk through the real Windows process table (via powershell.exe/Win32_Process) once the POSIX-visible chain dead-ends with no harness match, and give fm_harness_pid_alive the matching Windows-native liveness path for a pid recorded that way. Verified live against this session: ancestry resolution now finds claude.exe and fm_harness_pid_alive confirms it live. The pre-existing e2e fixtures in fm-session-lock-ancestry.test.sh and fm-watcher-lock.test.sh fail the same way on unmodified main in this environment (their own fixtures poll `ps -o ppid=` to detect orphaning), so that gap is unchanged by this commit, not introduced by it.
Git Bash/MSYS 'ln -s' without symlink privilege silently copies the owner directory instead of linking, so the readlink ownership handshake never verifies, the copy is left behind as an un-owned claim, and fm_lock_acquire_wait spins forever treating its own copied claim (whose pid file names the live acquiring shell) as a foreign live holder. This is the claim-lock hang observed after the ancestry fix on Windows. A directory junction needs no privilege, satisfies [ -L ], round-trips the owner path through readlink as a POSIX path, and is removed by 'rm -f' like any symlink. Invoke mklink under MSYS2_ARG_CONV_EXCL so the MSYS runtime does not rewrite /J or the Windows paths on their way to cmd.exe. Non-Windows platforms keep the plain 'ln -s' path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a second commit to this branch: 2d57d82 fixes the claim-lock hang that remained after the ancestry fix. On Git Bash/MSYS without symlink privilege, 'ln -s' silently copies the owner directory instead of linking, so the readlink ownership handshake never verifies and fm_lock_acquire_wait spins forever on its own copied claim (this also produced the nested .steal.steal debris). The fix creates claim locks as directory junctions (mklink /J, no privilege needed) on Windows, invoked under MSYS2_ARG_CONV_EXCL so the MSYS runtime does not mangle the cmd.exe arguments. Non-Windows platforms keep plain 'ln -s'. Verified end-to-end on Windows 11 Home / Git Bash: session lock acquires in under a second, repeat acquires are stable, and a full session-start digest runs the mutating bootstrap path. |
Summary
bin/fm-session-lock-lib.sh's ancestry walk relies onps -o comm=/args=/ppid=, which Git for Windows' bundled Cygwinpsdoes not support at all (only-aefls/-p/-u/-W). Every call failed outright, sofm-lock.shreportedcannot locate harness process in ancestryon every Windows session and the session could never acquire its lock./proc/<pid>/{exename,cmdline,ppid}is read as a same-host fallback only whenps -oproduced nothing, which keeps Linux/macOS behavior unchanged (they never hit that branch).bash.exea nativeclaude.exelaunches, since each Bash-tool invocation is a fresh, otherwise-unrelated subprocess. The real ancestry still exists at the Windows OS level, so the walk now continues through the real Windows process table (Win32_Processviapowershell.exe) once the POSIX-visible chain dead-ends without a harness match, andfm_harness_pid_alivegained the matching Windows-native liveness check for a pid recorded that way.Verified live in the environment that surfaced the bug:
fm_harness_ancestry_pidnow resolves to the realclaude.exeprocess, andfm_harness_pid_alive/fm_harness_winpid_alivecorrectly confirm it live.Known pre-existing gap (not introduced by this change)
tests/fm-session-lock-ancestry.test.sh's e2e cases andtests/fm-watcher-lock.test.shalso fail in this same Windows/Cygwin environment on unmodifiedmain- their own fixtures pollps -o ppid=to detect when a process has been orphaned, which is the identical incompatibility this PR fixes in the library itself. I left the test fixtures alone to keep this change scoped to the reported bug; a follow-up should teach those fixtures the same/procfallback.Test plan
tests/fm-session-lock-ancestry.test.shunit-layer cases (fake-psdriven) pass unchanged.fm_harness_ancestry_pidresolves the real harness process;fm_harness_pid_aliveconfirms liveness.shellcheckcould not be run locally (not installed in this environment); CI's lint gate should verify.fm-watcher-lock.test.shfailures pre-date this change (same failures on unmodifiedmainin this environment).