Paste images from the Windows clipboard into Claude Code (or any other Linux app reading the Wayland clipboard) when it's running inside WSL launched from Windows Terminal.
Cyntax-maintained fork of
rajveerb/wsl-clip-bridge. All credit for the original design goes to Rajveer Bachkaniwala — see Credits. This fork hardens packaging (forced LF line endings, CI build, corrected Go version requirement) for a clean WSL install.
Blog post (original author): Why Ctrl+V won't paste images in Claude Code on WSL, with a fix
Out of the box, copying an image in Windows (e.g. Win+Shift+S) and trying
to paste it into a Claude Code session inside WSL does nothing. Three reasons:
- WSLg only forwards Windows → Linux images as
image/bmp(microsoft/wslg#833), and those BMPs are BI_BITFIELDS-encoded, which Claude Code's decoder cannot read (anthropics/claude-code#50552). - WSLg's reverse sync silently overwrites any PNG you put on the Linux clipboard moments later — and the overwrite produces no Windows-side event you can hook.
- Windows Terminal grabs
Ctrl+Vat the terminal layer to paste text, so the keystroke never reaches Claude Code's TUI and itschat:imagePastehandler never fires.
This repo fixes all three: an event-driven Win32 listener encodes the
clipboard image as PNG, a bash bridge pushes it onto the WSL Wayland
clipboard and re-asserts once after WSLg's overwrite, and an alt+v
keybinding sidesteps the Windows Terminal Ctrl+V intercept.
git clone https://github.com/CyntaxDotDev/wsl-clip-bridge.git
cd wsl-clip-bridge
sudo apt install wl-clipboard
./install.sh --with-systemd --with-keybindingSnip an image in Windows (Win+Shift+S) and press Alt+V in
Claude Code. The image attaches.
./install.sh --help shows all flags.
If you'd rather see exactly what's happening (or run the steps by hand
instead of via install.sh), here is the full procedure.
- WSL2 with WSLg. Windows 11 ships with this; on Windows 10 you need
a recent WSL update (
wsl --update). - Go 1.26+ on the Linux side (required by
go.mod; used to cross-compile the Windows binary). If you don't have it: download from go.dev/dl — no sudo needed if you extract into~/.local/go.install.shauto-detects~/.local/go/bin/go, agoonPATH, or an explicitGO_BIN=/path/to/go. wl-clipboard:sudo apt install wl-clipboard
The listener is windows/clip-listener/main.go. Cross-compile it for
Windows from inside WSL:
cd windows/clip-listener
GOOS=windows GOARCH=amd64 go build \
-trimpath -ldflags="-s -w" \
-o clip-listener.exe .You should end up with a ~2 MB clip-listener.exe.
mkdir -p ~/.local/bin ~/.local/share/wsl-clip-bridge
install -m 0755 windows/clip-listener/clip-listener.exe \
~/.local/share/wsl-clip-bridge/clip-listener.exe
install -m 0755 wsl/wsl-clip-bridge \
~/.local/bin/wsl-clip-bridgeMake sure ~/.local/bin is on your PATH. (Most distros do this by
default if the directory exists.)
Do not start the bridge from a shell (nohup, .bashrc, etc.).
A Windows exe launched through WSL interop is tied to the WSL session
that spawned it: close that terminal and clip-listener.exe dies
silently. Worse, the /init interop stub keeps the stdout pipe open, so
the bash side never reads EOF and keeps running as a zombie that thinks
the listener is still alive. This was the cause of the recurring
"pasting stopped working again" failures.
A systemd user service is session-independent and restarts the chain
automatically (the script's built-in watchdog polls the Windows process
table and exits when the exe is gone, so Restart=always kicks in):
mkdir -p ~/.config/systemd/user
install -m 0644 wsl/wsl-clip-bridge.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now wsl-clip-bridge.serviceRequires systemd in WSL ([boot] systemd=true in /etc/wsl.conf,
default on recent Ubuntu images). If loginctl show-user $USER -p Linger
says no, run sudo loginctl enable-linger $USER so the service starts
with the distro instead of with your first login session.
Create ~/.claude/keybindings.json (or add the binding to your existing
one):
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"bindings": [
{ "context": "Chat", "bindings": { "alt+v": "chat:imagePaste" } }
]
}This binds Alt+V to Claude Code's image-paste handler. Windows Terminal
doesn't grab Alt+V, so the keystroke reaches Claude Code.
Open a fresh WSL shell. Then:
# In Windows, copy an image (Win+Shift+S, snip a region).
# Back in WSL:
wl-paste -l # should include "image/png"
wsl-clip-bridge --status # should say "running"
# In Claude Code: press Alt+V. The image attaches.| Path | Purpose |
|---|---|
~/.local/share/wsl-clip-bridge/clip-listener.exe |
Go-built Win32 listener (~2 MB) |
~/.local/bin/wsl-clip-bridge |
Bash daemon: pipes listener → wl-copy, watchdogs the exe |
~/.config/systemd/user/wsl-clip-bridge.service |
Session-independent start + auto-restart |
~/.claude/keybindings.json |
alt+v → chat:imagePaste |
~/.cache/wsl-clip-bridge/ |
Saved PNGs + bridge.log |
Windows side WSL side
──────────── ──────────
Win+Shift+S
│ sets BMP on Windows clipboard
▼
WM_CLIPBOARDUPDATE
│
clip-listener.exe (Go, event-driven, ~3-8 MB resident)
• AddClipboardFormatListener on a message-only window
• blocks in GetMessage
• on each event: HBITMAP → GdiPlus encode → PNG file
• SHA-256 + 3s dedup window kills the WSLg round-trip
│
│ stdout: "IMAGE /path/clip-N.png"
└───── pipe ────────► wsl-clip-bridge (bash)
• wl-copy --type image/png
• sleep 0.5s, then re-assert once
if WSLg has overwritten our PNG
│
▼
Wayland clipboard = image/png
│ Alt+V
▼
Claude Code
chat:imagePaste
→ wl-paste image/png
→ image attached
Event-driven end-to-end except for the single re-assertion check after each emit, which exists to recover from WSLg's silent Windows→Wayland clobber.
wsl-clip-bridge --status # is clip-listener.exe alive? + service state
wsl-clip-bridge --doctor # check health; restart the service if broken
wsl-clip-bridge --stop # stop the service and kill the listener
systemctl --user status wsl-clip-bridge.service
tail -f ~/.cache/wsl-clip-bridge/bridge.logIf pasting suddenly stops working, wsl-clip-bridge --doctor is the one
command to try: it checks whether clip-listener.exe is running and
restarts the systemd service if not. With the watchdog in place this
should rarely be needed — a dead listener is detected within ~10 seconds
and the service restarts itself (look for watchdog: lines in
bridge.log).
If a future Claude Code release reads image/bmp correctly and
Windows Terminal stops eating Ctrl+V (or you unbind it manually in
Terminal Settings → Actions), the whole bridge becomes redundant —
not incorrect, just unnecessary. Removal is:
systemctl --user disable --now wsl-clip-bridge.service
rm -f ~/.config/systemd/user/wsl-clip-bridge.service
rm -rf ~/.local/share/wsl-clip-bridge ~/.local/bin/wsl-clip-bridge
# and remove the Chat-context alt+v entry from ~/.claude/keybindings.jsonNothing in the rest of your environment depends on this.
This project is a fork of
rajveerb/wsl-clip-bridge
by Rajveer Bachkaniwala, who designed and built the original
event-driven listener, the WSLg round-trip dedup, and the bash bridge —
the hard parts. See the original
blog post
for the full backstory.
This Cyntax fork adds packaging hardening on top of that work:
- systemd user service + watchdog. The interop-launched exe dies
silently when its spawning WSL session closes, and the
/initstub keeps the pipe open so the bash side never notices. The bridge now runs session-independent under systemd; a watchdog in the script polls the Windows process table and exits when the exe is gone, soRestart=alwaysbrings the whole chain back within seconds. .gitattributesforcing LF endings so the scripts survive a clone withcore.autocrlf=true(the#!/usr/bin/env bash\rfailure on WSL).- GitHub Actions CI that cross-compiles
clip-listener.exeand lints the shell scripts on every push. - Corrected the documented Go requirement to match
go.mod(1.26+).
Licensed under the MIT License; the original copyright is retained alongside the fork's.