09/15/2026
ln -s in Git Bash Copies Instead of Linking (and Exits 0)
Why doesn't ln -s create a symlink in Git Bash?
By default it makes a copy. Git Bash runs on the MSYS2 runtime, and the MSYS2 documentation states that its default mode "is called winsymlinks:deepcopy". The Git for Windows documentation says the same thing more bluntly: ln -s "creates copies."
So this:
ln -s shared-config link-to-config
gives you a real directory holding a copy of shared-config. The exit status is 0. Nothing warns you. Edit the "link" and the original doesn't change, which you usually find out much later.
I found it by checking rather than trusting: islink was false, dir /al showed no reparse point, and the command had exited successfully.
How to get a real symlink
Two things have to be true.
1. Windows has to let you create symlinks. That takes the SeCreateSymbolicLinkPrivilege privilege, which the Git for Windows docs note is "by default assigned only to Administrators and guarded by UAC". Since Windows 10 version 1703, turning on Developer Mode lifts that restriction for normal users (Settings → System → For developers).
2. Tell the MSYS runtime to make native links:
export MSYS=winsymlinks:nativestrict
ln -s shared-config link-to-config
Put the export in ~/.bashrc so every Git Bash session gets it.
Use nativestrict, not native
Both values ask for native Windows symlinks. They differ in what happens when that isn't possible. The Cygwin documentation, which MSYS2 inherits this setting from, says that when a native symlink can't be created, winsymlinks:native "will fall back to creating Cygwin default symlinks", while with winsymlinks:nativestrict the call "will immediately fail."
A loud failure is the point. With native you're back to something that isn't a real Windows symlink, and exit code 0 again.
How to check you actually got a symlink
Don't trust the exit code. Ask the filesystem:
test -L link-to-config && echo "symlink" || echo "NOT a symlink"
From cmd:
dir /al
A real link is listed as <SYMLINKD> (directory) or <SYMLINK> (file). A copy isn't listed at all.
From PowerShell:
(Get-Item link-to-config).LinkType
That prints SymbolicLink for a real one, and nothing for a copy.
If you only need it once
Skip the MSYS setting and call Windows directly. Quote the whole mklink command: Git Bash rewrites arguments that look like paths, and an unquoted /D doesn't survive it (cmd answers Invalid switch). The doubled slash in //c protects that argument the same way:
cmd //c "mklink /D link-to-config shared-config"
/D is for a directory. Leave it off for a file.
FAQ
Does this affect symlinks inside a Git repository?
That's a separate setting. Git decides whether to check out symlinks as links using core.symlinks; see the Git for Windows page linked above.
Why is copying the default at all? Native symlinks need a privilege most Windows accounts don't have, and a copy always works. It's a safe default for tools that just need the files to be there, and a trap for anything that expects both paths to stay in sync.