The "Why is Python broken again?" moment#
Let me say this right from the start: at first glance, this error looks like "Python is broken," but it's not. Here's what actually happens: you run python inside WSL, and it throws this at you:
/mnt/c/Users/user/.pyenv/pyenv-win/shims/python
bad interpreter: /bin/sh^M
And your first thought is: "What did I do now?" First reaction: What happened to Python? Second reaction: Did I break my .zshrc? Third reaction: Should I throw the computer at the wall? I went through exactly that order. And finally, I realized that the issue had nothing to do with Python; it was entirely a PATH conflict.
WSL Interop: The Silent Killer#
I need to get a bit technical here, but don't worry, I'll explain. WSL has an "interop" mechanism with Windows. This mechanism allows you to run Windows applications from within WSL — for example, typing explorer.exe . opens the file manager, and notepad.exe opens Notepad. It's a nice feature. But there's a problem. This interop mechanism silently pulls the Windows PATH into WSL as well. This means everything installed on Windows — Python, Node.js, Java, you name it — leaks into WSL's PATH. Look at what Microsoft's own documentation says: WSL Global Configuration Options There's a setting called appendWindowsPath. By default, it's true. So the Windows PATH is automatically added. When you read it, you think, "Okay, this is optional, leave it alone." Then you look at your system, and it's half Windows, half Linux. Sounds like a great hybrid, right? No. It's not.
My Experience: Debugging Log#
First, I tinkered with .zshrc. I examined the PATH lines one by one. They were clean. I checked .profile. Clean as well. I ran printenv | grep -i pyenv. Output:
PATH=...:/mnt/c/Users/user/.pyenv/pyenv-win/bin:...
That's where I saw it. Pyenv isn't installed on the Linux side, but pyenv-win on Windows leaked into WSL's PATH. In case you don't know how pyenv-win works, let me briefly explain: it's a tool that manages Python versions on Windows. It creates fake python executors in its own shims folder. When WSL sees these shims, it says, "Ah, okay, Python is here," but those shims are written for Windows. Then comes the bad interpreter: /bin/sh^M error. Did you see the ^M? That's the Windows line-ending character (\r\n). Linux requires \n. So the file coming from Windows cannot be read by Linux.
How Does Interop Work? (Technical Detail)#
Let's dive a bit deeper. When WSL starts, it works in this order:
- The Linux kernel loads.
- The init process begins.
/etc/wsl.confand%UserProfile%\.wslconfigare read.- If
enabled=true, information is pulled from Windows. - If
appendWindowsPath=true, the Windows PATH is added to the Linux PATH.
During this process, the pyenv-win folder on the Windows side ends up in the PATH. And the bash in Linux tries to run the shims files in that folder. Result: bad interpreter error. There are people on StackOverflow experiencing the same error. Everyone there says the same thing: "Windows installs pyenv, it leaks into WSL, Python breaks."
The Solution: Step-by-Step Isolation#
I applied the simplest but most drastic solution. I completely isolated WSL from Windows.
Step 1: Find or Create the wsl.conf File#
Open a terminal in WSL:
sudo nano /etc/wsl.conf
It's okay if the file doesn't exist; nano will create it anyway.
Step 2: Change Interop Settings#
Write the following inside the file:
[interop]
enabled=false
appendWindowsPath=false
What do these two lines do?
enabled=false: Disables running Windows applications from within WSL.appendWindowsPath=false: Prevents the Windows PATH from leaking into WSL.
Save it: Ctrl+X, then Y, then Enter.
Step 3: Restart WSL#
Exit the terminal and in PowerShell:
wsl --shutdown
Then open WSL again. Without this command, the changes will not take effect.
Step 4: Verification#
Enter WSL again and check:
echo $PATH
You should no longer see Windows PATHs starting with /mnt/c/....
python --version
If it doesn't find Python — that's normal. You need to install Python on the Linux side. But at least there's no more broken Python leaking from Windows.
Comparison: Interop Enabled vs. Disabled#
Think about it before doing this. There are advantages and disadvantages to disabling interop:
| Interop Enabled | Interop Disabled | |
|---|---|---|
| Windows Apps | Run from WSL | Do not run |
| PATH Security | Risky — Windows can leak | Secure — full isolation |
| Performance | Slightly slower (Windows PATH scan) | Faster |
| File Access | /mnt/c/ works |
/mnt/c/ still works1 |
| VS Code Integration | code . works |
You need to connect manually2 |
| Python/Node Security | Can get mixed up | Entirely under Linux control |
Side Effects (Small But Important)#
After doing this, I noticed that WSL no longer acts like a "Windows + Linux hybrid environment" but more like a small Linux VM. And to be honest… I liked it better this way. But some things changed:
explorer.exe .no longer works. You can usewslview .instead (requires thewslupackage).- You cannot call
.exefiles from Windows. But why would you want to, anyway? - Docker Desktop WSL backend might be affected. If you use Docker, research how Docker works with WSL before making this setting.
Frequently Asked Questions#
"VS Code doesn't work after setting appendWindowsPath=false"#
The VS Code WSL extension (Remote - WSL) already connects to WSL over SSH. It works even if interop is disabled. Only the code . command doesn't work. Solution: Open VS Code from Windows, click the "><" icon in the bottom left, and select "Connect to WSL."
"Is wsl --shutdown required?"#
Yes. wsl.conf changes only take effect when WSL is restarted. wsl --shutdown closes all WSL instances. Then typing wsl again opens it with the new settings.
"Is the same true for different distributions?"#
Yes. Ubuntu, Debian, Fedora, Arch — regardless of which WSL distribution you use, /etc/wsl.conf works the same way. But you may need to edit it separately for each distribution.
"Will Docker Desktop be affected?"#
If you are using Docker Desktop with the WSL backend, yes, it could be affected. Docker has its own WSL instance (docker-desktop). You might need to edit its /etc/wsl.conf separately. Or just isolate your development WSL instance and leave Docker alone.
"How do I install Python now?"#
Inside WSL:
sudo apt update && sudo apt install python3 python3-pip
If you want multiple Python versions, install them with the Linux version of pyenv, as I explained in my Linux GPU driver guide, not with system packages. But this time only your Linux pyenv will work, and there will be no shims leaking from Windows.
Why Isn't "appendWindowsPath=false" Enough?#
You might ask, "Wait, isn't it enough to just block the PATH?" No. Because while interop is on, some things coming from Windows can still cause problems. For example:
.batand.cmdfiles in Windows can be called from WSL.- Windows environment variables (like
%APPDATA%) can leak into WSL. - Some tools (especially Java, .NET) try to use paths coming from Windows.
I preferred total isolation. You can try a lighter solution by just setting appendWindowsPath=false. But in my experience, half-solutions tend to cause headaches later.
Alternative Solution: Removing Only Specific PATHs#
If you don't want to disable interop completely, you can remove only the problematic PATHs:
# Add to .zshrc or .bashrc to run automatically when WSL opens:
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/mnt/c' | tr '\n' ':' | sed 's/:$//')
This command removes all /mnt/c/... paths from the PATH. But this is a temporary solution — you need to run it in every WSL session.
I chose the permanent solution: I solved it at the root with wsl.conf.
Conclusion (Let's Be Honest)#
I think the real lesson here is this: if you use WSL as "Linux attached to Windows," at some point, Windows infects everything. This is true not just for pyenv but for everything. Node.js, Java, Ruby, Go — any tool installed on Windows can leak into WSL's PATH. I eventually did this:
- I cut the Windows PATH.
- I disabled interop.
- I isolated the system.
And strangely enough… everything was fixed 😄 WSL is a great tool, but you either use it like Linux or you get lost in the Windows mess. I left the second way and moved to the first. And I'm glad I did. If you've run into a similar pyenv dead end on the Windows side, my guide on Powershell pyenv-win Installation Error Solution is exactly what you need!
A small confession: When I first solved this, I was quite in the mode of 'why did Python become my enemy?' It turns out it wasn't Python; it was the environment. I learned about this thing called interop that day. And I've been using WSL isolated ever since.
Comments