Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

wsl-clip-bridge

CI License: MIT

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

Why this exists

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:

  1. 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).
  2. 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.
  3. Windows Terminal grabs Ctrl+V at the terminal layer to paste text, so the keystroke never reaches Claude Code's TUI and its chat:imagePaste handler 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.

Quick start

git clone https://github.com/CyntaxDotDev/wsl-clip-bridge.git
cd wsl-clip-bridge
sudo apt install wl-clipboard
./install.sh --with-systemd --with-keybinding

Snip an image in Windows (Win+Shift+S) and press Alt+V in Claude Code. The image attaches.

./install.sh --help shows all flags.

Step-by-step setup

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.

1. Prerequisites

  • 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.sh auto-detects ~/.local/go/bin/go, a go on PATH, or an explicit GO_BIN=/path/to/go.
  • wl-clipboard:
    sudo apt install wl-clipboard

2. Build the Windows listener

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.

3. Install both pieces

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-bridge

Make sure ~/.local/bin is on your PATH. (Most distros do this by default if the directory exists.)

4. Run as a systemd user service

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.service

Requires 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.

5. Add the Claude Code keybinding

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.

6. Verify

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.

What lands where

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+vchat:imagePaste
~/.cache/wsl-clip-bridge/ Saved PNGs + bridge.log

How it works

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.

Useful commands

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.log

If 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).

Retiring this

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.json

Nothing in the rest of your environment depends on this.

Credits

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 /init stub 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, so Restart=always brings the whole chain back within seconds.
  • .gitattributes forcing LF endings so the scripts survive a clone with core.autocrlf=true (the #!/usr/bin/env bash\r failure on WSL).
  • GitHub Actions CI that cross-compiles clip-listener.exe and 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.

About

Paste Windows clipboard images into Claude Code (and any Wayland app) inside WSL. Event-driven Win32 listener + bash bridge. Cyntax-maintained fork.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages