Re-register the WSLInterop binfmt_misc entry in a running WSL2 distro so that you can run Windows .exe files from a Linux shell again, without needing wsl --shutdown.
You're in a WSL2 shell. You try to run a Windows executable (wsl.exe, notepad.exe, cmd.exe, anything) from bash, and instead of running, you see:
/mnt/c/Users/you/AppData/Local/Microsoft/WindowsApps/wsl.exe: line 1: MZ: command not found
Or if you tried cmd.exe:
/mnt/c/Windows/System32/cmd.exe: line 1: MZ@: command not found
bash is treating the Windows binary as a shell script. The first two bytes of every Windows PE executable are M and Z (the magic of the old DOS header), so bash reads MZ as a command name and obviously can't find it.
Direct check:
ls /proc/sys/fs/binfmt_misc/
# register status
# (no WSLInterop)If WSLInterop is missing from /proc/sys/fs/binfmt_misc/, you've hit this bug. The Linux kernel doesn't know to dispatch MZ-prefixed files to the WSL /init interpreter anymore.
WSLInterop is the binfmt_misc kernel entry that tells Linux: "When a file starts with the bytes MZ, run it via /init." WSL registers this entry at distro boot and (when systemd is enabled) protects it via an override.conf for systemd-binfmt.service.
But binfmt_misc is shared across all running WSL2 distros. You can confirm with:
$ cat /proc/self/mountinfo | awk '$3=="binfmt_misc"'
121 114 0:35 / /proc/sys/fs/binfmt_misc rw,relatime shared:22 - binfmt_misc binfmt_misc rw
The shared:22 is the smoking gun: this filesystem is in a shared mount propagation peer group. Changes made to binfmt_misc in any one WSL2 distro propagate to all of them.
That means any of the following can wipe WSLInterop for every running distro:
- A distro starting up without WSL's
protectBinfmtoverride (e.g.systemd=falsein/etc/wsl.conf, or a custom init). wsl --terminate <some-other-distro>leavingbinfmt_miscin an inconsistent state.- A QEMU multi-arch helper container (
docker run --privileged multiarch/qemu-user-static) calling--unregisteron the global table. - A race between two distros booting near-simultaneously: one's
echo -1racing the other'secho register. - Any tool, in any distro, that writes
-1to/proc/sys/fs/binfmt_misc/status(which clears the entire table).
Normally the only recovery is wsl --shutdown, which fully restarts the WSL2 utility VM. That kills every running WSL session — annoying if you have a long-running shell, an editor, a build, or an AI agent (Claude Code, Cursor, etc.) running inside a distro.
This script avoids the shutdown.
This is rarely the result of anything you did inside the distro you're working in. It's almost always a side effect of something else on the same Windows host touching binfmt_misc in another distro or in a privileged container. Observed triggers:
- Rancher Desktop starting or restarting. Rancher Desktop runs its container engine inside its own managed WSL2 distro and manipulates
binfmt_miscas part of bringing the runtime up. Becausebinfmt_miscpropagates across distros, theWSLInteropentry can disappear from every other running distro at the moment Rancher starts. (This is what motivated writing this tool.) - Docker Desktop starting, restarting, or installing. Same general pattern as Rancher Desktop — Docker Desktop's WSL backend touches
binfmt_miscfor cross-arch image support, which collides withWSLInterop. - Booting a "minimal" WSL2 distro without WSL's
systemd-binfmtoverride. Distros that ship with[boot] systemd=false(Parrot Security is a known example), or that have a non-standard init, or where the user has setprotectBinfmt=falsein/etc/wsl.conf. When these come up, their/initmay registerWSLInteroplater than another distro's reset, or never at all. docker run --privileged multiarch/qemu-user-static --resetor similar QEMU multi-arch setup containers. These call--unregisteron/proc/sys/fs/binfmt_misc/status, which wipes the entire table — includingWSLInterop.wsl --terminate <some-other-distro>while you have a session open in this one. Teardown of one distro can leave the globalbinfmt_miscstate inconsistent.- Two distros booting near-simultaneously. A race between their
/initprocesses'echo -1 > WSLInterop; echo registersequences can land in a state with noWSLInteropregistered. - AI coding agents (Claude Code, Cursor, Aider, etc.) running inside a WSL distro that themselves drive
wsl.exeto query or modify other distros. The first time interop breaks in their session, they typically cannot recover on their own because they can't runwsl --shutdownfrom within the distro they're running in. This script + a scoped passwordless-sudo grant lets them recover transparently. (See the Install section.)
If you experience this regularly, the most common culprit on Windows desktops is having Docker Desktop or Rancher Desktop installed alongside other WSL2 distros that you also use for development.
/usr/local/sbin/wsl-fix-interop does exactly one thing:
printf ':WSLInterop:M::MZ::/init:P\n' > /proc/sys/fs/binfmt_misc/registerThat's the exact same line WSL's own /init writes at distro boot (and the same line WSL's systemd override re-asserts). It re-registers the MZ → /init dispatch. Because binfmt_misc is shared across all distros, running this script in any one distro restores interop everywhere.
The script:
- Checks
/proc/sys/fs/binfmt_misc/WSLInteropfirst; exits cleanly if already registered. Idempotent — safe to call speculatively. - Writes the fixed literal string (no input substitution), so even via
sudo NOPASSWDit has no attack surface. - Is root-owned and root-writable only, so a passwordless-sudo grant for it isn't transitive.
The companion sudoers fragment at /etc/sudoers.d/fix-binfmt grants your user NOPASSWD for only this one script path, nothing else.
The binfmt entry stores the literal string /init as the interpreter — not a resolved inode. When the kernel later dispatches an MZ-prefixed file to /init, it resolves the path in the executing process's mount namespace. So even though distro A may have done the registration, when you run wsl.exe from distro B, the kernel runs B's /init. Cross-distro repair works correctly.
Clone the repo and run the installer as root:
git clone https://github.com/rfay/wsl-fix-interop.git
cd wsl-fix-interop
sudo bash install.shBy default, the installer grants passwordless sudo for the script to $SUDO_USER. Override with an explicit username:
sudo bash install.sh someuserThe installer:
- Copies
wsl-fix-interopto/usr/local/sbin/wsl-fix-interop(mode 0755, root:root). - Writes
/etc/sudoers.d/fix-binfmtgranting only that user only passwordless sudo for only that script path. The fragment is validated withvisudo -cfbefore being installed, so a syntax error can't lock you out. - Runs a smoke test to confirm the user really can invoke it without a password.
When interop breaks (you see MZ: command not found or similar):
sudo wsl-fix-interopThat's it. Run any .exe again — it should work. The script prints whether it actually did anything:
wsl-fix-interop: WSLInterop re-registered.
…or if there was nothing to do:
wsl-fix-interop: WSLInterop already registered, nothing to do.
Don't need to. The shared binfmt_misc mount means a repair from any one distro fixes all of them. Install it in your primary distro — the one whose shell you're most often in — and call from there when needed.
Some sources suggest setting appendWindowsPath=false in /etc/wsl.conf as a workaround. That doesn't fix this problem at all — it changes which Windows directories appear in PATH, but binfmt_misc is what dispatches the MZ-prefixed file, not PATH. Without WSLInterop registered, you can't run Windows binaries even if you give an absolute path.
Apache 2.0 — see LICENSE. Use at your own risk; it pokes at a kernel sysfs interface.
- Linux kernel binfmt_misc docs: https://docs.kernel.org/admin-guide/binfmt-misc.html
- WSL
wsl.confreference (look forprotectBinfmt): https://learn.microsoft.com/en-us/windows/wsl/wsl-config - The WSL-generated protective override (readable inside any WSL2 distro running systemd):
/run/systemd/generator/systemd-binfmt.service.d/override.conf