Skip to content

fix(lock): acquire the session lock on Windows/Git Bash - #3023

Closed
seanofabob wants to merge 1 commit into
kunchenguid:mainfrom
seanofabob:fix/windows-session-lock-ancestry
Closed

seanofabob wants to merge 1 commit into
kunchenguid:mainfrom
seanofabob:fix/windows-session-lock-ancestry

Conversation

@seanofabob

Copy link
Copy Markdown

Problem

On Windows / Git Bash (MSYS), the per-home session lock never acquires, so every session is forced into permanent read-only mode. Three layered platform defects combine to cause it:

  1. Harness-ancestry walk uses unsupported ps flags. fm_harness_ancestry_pids reads the process tree with ps -o comm= -p and ps -o ppid= -p. The MSYS ps bundled with Git Bash rejects -o ("unknown option -- o"), so the walk fails on its first call and fm_harness_ancestry_pid never resolves the harness.

  2. Spawned scripts are orphaned from the harness. MSYS emulates fork() through a helper that then exits, so a script launched as a child (session-start spawns fm-lock.sh) has a Win32 parent pid pointing at an already-dead launcher. No parent-chain walk can climb from it to claude.exe, even with a native process query.

  3. The symlink-based lock primitive silently copies. fm_lock_try_create builds every lock with ln -s. MSYS's default ln -s copies the target instead of creating a symlink, so fm_lock_points_to_owner never validates and fm_lock_acquire_wait spins forever.

Fix

  • Native Windows ancestry walk (fm-session-lock-lib.sh): read the process tree from a single Win32_Process query and climb Windows PIDs, handing the same (comm, args) evidence to the existing fm_harness_process_matches contract.
  • CLAUDE_PID anchor: Claude Code exports its own pid to every descendant; it survives the orphaning. Anchor the walk there when it names a live harness, falling back to the honest parent-chain walk when absent or stale.
  • Native symlinks (fm-wake-lib.sh): request winsymlinks:nativestrict on Windows so ln -s creates a real NTFS symlink (needs Developer Mode / SeCreateSymbolicLink) instead of copying.

All Windows behavior is gated on OSTYPE / FM_FORCE_WINDOWS; the POSIX paths are byte-for-byte unchanged.

Tests

  • tests/fm-session-lock-ancestry.test.sh: new cases inject a deterministic Windows process table (FM_WIN_PROCTABLE_CMD / FM_WIN_SELF_WINPID / FM_FORCE_WINDOWS) and assert anchor resolution, orphan rescue, parent-walk resolution, fail-closed on an invalid/stale anchor, and that an unrelated harness never owns the lock. Existing POSIX cases are pinned to the POSIX path so they stay host-independent; the POSIX-ps e2e fixtures skip on hosts without ps -o.
  • tests/fm-wake-queue.test.sh: asserts a Windows source requests native symlinks, preserves an operator's explicit policy, and is a no-op off Windows.

Verified on Windows 10 / Git Bash: the lock now acquires (lock acquired: harness pid <n>), holds idempotently, and reports correct status; the new tests pass, and shellcheck (0.11.0, LF-normalized) is clean.

🤖 Generated with Claude Code

The per-home session lock never acquired under Git Bash/MSYS, forcing every
Windows session into read-only mode. Three layered platform defects caused it:

- The harness-ancestry walk used `ps -o comm=` / `-o ppid=`, which the MSYS ps
  bundled with Git Bash rejects, so the walk failed on its first call. Add a
  native Windows path that reads the process tree from a single Win32_Process
  query and climbs Windows PIDs, handing the same (comm, args) evidence to the
  existing harness-identity contract.
- MSYS fork emulation orphans spawned scripts: a child's Win32 parent pid names
  an already-exited launcher, so no parent-chain walk can reach the harness.
  Anchor the walk on CLAUDE_PID, which Claude Code exports to every descendant
  and which survives the orphaning, validated against a live harness process and
  falling back to the honest walk when absent or stale.
- fm_lock_try_create builds each lock with `ln -s`, which MSYS silently turns
  into a copy, so fm_lock_acquire_wait spun forever. Request
  winsymlinks:nativestrict so ln -s makes a real NTFS symlink.

The Windows paths are gated on OSTYPE/FM_FORCE_WINDOWS and leave the POSIX paths
byte-for-byte unchanged. Adds cross-platform regression tests that inject a
deterministic Windows process table and assert the anchor, orphan-rescue,
parent-walk, fail-closed, and native-symlink-request behavior on any host.
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The PR should not merge until existing non-strict winsymlinks policies are handled without leaving Windows lock acquisition in an infinite retry.

The new policy guard preserves Windows symlink modes that can still copy the lock target, so the lock primitive fails validation and its unbounded acquisition loop never completes.

Files Needing Attention: bin/fm-wake-lib.sh and tests/fm-wake-queue.test.sh

Reviews (1): Last reviewed commit: "fix(lock): acquire the session lock on W..." | Re-trigger Greptile

Comment thread bin/fm-wake-lib.sh
case "${OSTYPE:-}" in
msys*|mingw*|cygwin*)
case "${MSYS:-}" in
*winsymlinks*) : ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Non-strict policy preserves lock failure

If MSYS already selects winsymlinks:native, winsymlinks:copy, winsymlinks:deepcopy, or winsymlinks:lnk, this branch preserves a mode that can copy the lock target instead of creating a native symlink. Lock validation then fails and fm_lock_acquire_wait retries indefinitely, leaving the session unable to acquire fleet locks.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: first look on current main 85d6c7226c93 (#2953). Never messaged the captain.

class=corrective. Windows/Git Bash session lock never acquired: MSYS ps rejects -o, fork-helper orphaning breaks parent walk, default ln -s copies. Native Win32_Process walk + CLAUDE_PID anchor + winsymlinks:nativestrict when no MSYS policy is set. POSIX path unchanged. Standing lock hold: do not auto-land a third lock PR.

VISION.md (inspected bin/fm-session-lock-lib.sh fm_is_windows / fm_win_harness_ancestry_pids / fm_win_harness_anchor_pid, bin/fm-wake-lib.sh MSYS symlink policy, tests/fm-session-lock-ancestry.test.sh / tests/fm-wake-queue.test.sh):

This HEAD: 1a021442901f51c28257fc1e024810699207804e. MERGEABLE / UNSTABLE, ahead 1 / behind 1.
Attestation missing. Greptile FAILURE (not a gate): existing non-strict winsymlinks policies are preserved, so ln -s can still copy and fm_lock_acquire_wait can retry without bound. Review note only; land hold is the lock family.

First-time fork (seanofabob has no other firstmate PRs; Behavior/Lint/CI had not run — Greptile-only does not count). Approved after full diff review. Workflows this pass: CI 32813622208, Require no-mistakes 32813622260. Not green at comment time.

Security: none that blocks CI approval. Local WMI process listing; test-only FM_WIN_PROCTABLE_CMD eval hook; no secrets/webhooks. CLAUDE_PID as lock identity is why the lock family stays a maintainer hold.

Overlap / standing hold: lock #2437 (OPEN) / #2839 (OPEN) / issue #2883 (OPEN). Do not squash a third lock PR. Not spawn-freshen / herdr.

Land-eligible rec: NO. Captain-flag NOW: no.

This is a captain-decision hold on the lock family (which mechanism, if any), not waiting-on-author for that product call. Fork CI was approved this pass and is not yet green. Not a merge I will recommend.

@seanofabob seanofabob closed this Aug 25, 2026
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.

2 participants