You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Windows: setupUserSeparation() symlinkSync fails with EPERM; the copy fallback promised in INSTALL.md never runs (directory junctions work unelevated) #1730
setupUserSeparation() in Tools/InstallEngine.ts creates the system/user separation link with plain symlinkSync(dataUserDir, liveUserDir) (lines 604 and 614, no type argument). On Windows, creating a real symlink requires elevation or Developer Mode, so on a stock machine symlinkSync throws EPERM; the engine reports symlink creation failed: EPERM ..., the separation link is never established, and checkSymlinkContract then fails with ... is not a symlink (system/user separation broken).
INSTALL.md line 163 promises otherwise:
| **Claude Code — Windows** | ✅ (copy fallback where symlinks need admin) | ✅ full |
No copy fallback exists in the v7.28.3 code path. Code and docs currently disagree about what happens on Windows.
Environment
LifeOS
v7.28.3 (release tarball)
OS
Windows 11 Home 10.0.26200
Harness
Claude Code v2.1.220
bun
1.3.14
git
2.51.0.windows.1
Repro
Windows 11, Developer Mode off, non-elevated PowerShell.
symlinkSync fails with EPERM: operation not permitted, symlink .... No fallback runs, and the symlink contract check fails.
Suggested fix: directory junctions
symlinkSync(dataUserDir,liveUserDir,"junction");
Why this works:
Directory junctions need no elevation and no Developer Mode.
Node and Bun ignore the type argument on non-Windows platforms, so no platform branch is needed.
dataUserDir is already absolute, which junctions require.
The existing contract survives unchanged: lstatSync().isSymbolicLink() returns true for junctions and readlinkSync() returns the target, which is all checkSymlinkContract() (Tools/InstallEngine.ts:625-639) relies on.
Verified on this install: after creating the junction manually,
checkSymlinkContract passed as written, and the installed system has been running against the junction since.
If the copy fallback described in INSTALL.md is still wanted as a last resort, junction-first is the better order: a copy forks USER state between the two locations, while a junction keeps the live-link semantics the separation design assumes. Either way, INSTALL.md and the code should agree.
Related Windows detection nits (can split into a separate issue if preferred)
detectTool() (Tools/InstallEngine.ts:108) and detectHarness() (Tools/InstallEngine.ts:132) probe with POSIX command -v, so on Windows the environment report claims bun, git, and sh are missing from PATH even when they are present and working.
The hook-interpreter probes check the POSIX execute bit (LIFEOS/TOOLS/Doctor.ts:582, LIFEOS/TOOLS/IntegrityCheck.ts:550), reporting registered hooks as not executable (chmod +x) on NTFS even though the hooks demonstrably execute via Git Bash.
Both are cosmetic, but they present as failures mid-install and make real errors harder to spot.
Summary
setupUserSeparation()inTools/InstallEngine.tscreates the system/user separation link with plainsymlinkSync(dataUserDir, liveUserDir)(lines 604 and 614, notypeargument). On Windows, creating a real symlink requires elevation or Developer Mode, so on a stock machinesymlinkSyncthrowsEPERM; the engine reportssymlink creation failed: EPERM ..., the separation link is never established, andcheckSymlinkContractthen fails with... is not a symlink (system/user separation broken).INSTALL.md line 163 promises otherwise:
No copy fallback exists in the v7.28.3 code path. Code and docs currently disagree about what happens on Windows.
Environment
Repro
HOMEset manually to get past the home-resolution bug, Windows: installer tools resolve home as process.env.HOME || "", so ScaffoldUser silently writes its USER tree into the release directory #1729).symlinkSyncfails withEPERM: operation not permitted, symlink .... No fallback runs, and the symlink contract check fails.Suggested fix: directory junctions
Why this works:
typeargument on non-Windows platforms, so no platform branch is needed.dataUserDiris already absolute, which junctions require.lstatSync().isSymbolicLink()returns true for junctions andreadlinkSync()returns the target, which is allcheckSymlinkContract()(Tools/InstallEngine.ts:625-639) relies on.Verified on this install: after creating the junction manually,
checkSymlinkContractpassed as written, and the installed system has been running against the junction since.If the copy fallback described in INSTALL.md is still wanted as a last resort, junction-first is the better order: a copy forks USER state between the two locations, while a junction keeps the live-link semantics the separation design assumes. Either way, INSTALL.md and the code should agree.
Related Windows detection nits (can split into a separate issue if preferred)
detectTool()(Tools/InstallEngine.ts:108) anddetectHarness()(Tools/InstallEngine.ts:132) probe with POSIXcommand -v, so on Windows the environment report claims bun, git, and sh are missing from PATH even when they are present and working.LIFEOS/TOOLS/Doctor.ts:582,LIFEOS/TOOLS/IntegrityCheck.ts:550), reporting registered hooks asnot executable (chmod +x)on NTFS even though the hooks demonstrably execute via Git Bash.Both are cosmetic, but they present as failures mid-install and make real errors harder to spot.