Skip to content

fix(bin): resolve harness ancestry on Windows/Cygwin sessions - #1864

Open
podledges wants to merge 2 commits into
kunchenguid:mainfrom
podledges:fix/windows-cygwin-session-lock-ancestry
Open

fix(bin): resolve harness ancestry on Windows/Cygwin sessions#1864
podledges wants to merge 2 commits into
kunchenguid:mainfrom
podledges:fix/windows-cygwin-session-lock-ancestry

Conversation

@podledges

Copy link
Copy Markdown

Summary

  • bin/fm-session-lock-lib.sh's ancestry walk relies on ps -o comm=/args=/ppid=, which Git for Windows' bundled Cygwin ps does not support at all (only -aefls/-p/-u/-W). Every call failed outright, so fm-lock.sh reported cannot locate harness process in ancestry on every Windows session and the session could never acquire its lock. /proc/<pid>/{exename,cmdline,ppid} is read as a same-host fallback only when ps -o produced nothing, which keeps Linux/macOS behavior unchanged (they never hit that branch).
  • Fixing that alone was not enough: 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 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_Process via powershell.exe) once the POSIX-visible chain dead-ends without a harness match, and fm_harness_pid_alive gained the matching Windows-native liveness check for a pid recorded that way.

Verified live in the environment that surfaced the bug: fm_harness_ancestry_pid now resolves to the real claude.exe process, and fm_harness_pid_alive/fm_harness_winpid_alive correctly confirm it live.

Known pre-existing gap (not introduced by this change)

tests/fm-session-lock-ancestry.test.sh's e2e cases and tests/fm-watcher-lock.test.sh also fail in this same Windows/Cygwin environment on unmodified main - their own fixtures poll ps -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 /proc fallback.

Test plan

  • tests/fm-session-lock-ancestry.test.sh unit-layer cases (fake-ps driven) pass unchanged.
  • Live-verified in a real Windows/Git-Bash/Cygwin session: fm_harness_ancestry_pid resolves the real harness process; fm_harness_pid_alive confirms liveness.
  • shellcheck could not be run locally (not installed in this environment); CI's lint gate should verify.
  • Confirmed the e2e-layer and fm-watcher-lock.test.sh failures pre-date this change (same failures on unmodified main in this environment).

podledges and others added 2 commits August 7, 2026 12:33
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>
@podledges

Copy link
Copy Markdown
Author

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant