Paste images and screenshots from your Windows clipboard into Claude Code running on a remote Linux server over SSH.
Take a screenshot with Win+Shift+S on your PC, press Alt+V inside claude on the server, and [Image #1] shows up. That's normally impossible because the server has no display and no clipboard of its own.
If you've seen any of these over SSH, this is the fix:
No image found in clipboard. You're SSH'd; try scp?Error: Can't open display: (null)fromxclip- Ctrl+V does nothing in Claude Code on a headless server, in VS Code Remote-SSH, or in tmux
macOS and Linux desktops work too (clipserve.py handles all three), but this guide and the troubleshooting log are Windows-focused.
On Linux, Claude Code's image paste shells out to xclip twice:
xclip -selection clipboard -t TARGETS -o # is there an image?
xclip -selection clipboard -t image/png -o # give me the PNG
On a headless server those calls fail. This repo puts a fake xclip on the server that answers both calls from your PC's clipboard instead:
Your Windows PC Remote server
─────────────── ─────────────
clipserve.py ◄── 127.0.0.1:8765 ──┐
(reads Windows clipboard │
via PowerShell) │ SSH reverse tunnel
│
clip-tunnel.bat ── ssh -N ─────────┴──────► ~/.clip.sock (unix socket, mode 0600)
(reconnect loop) ▲
│ curl --unix-socket
Alt+V in claude ──► ~/bin/xclip (shim)
clipserve.pyis a tiny HTTP server on your PC, standard library only, with three endpoints:/targets,/image,/text.- The SSH tunnel exposes it on the server as a unix socket that only your user can open.
remote/xclipis a bash shim that turns Claude Code'sxclipcalls intocurlrequests over that socket.
Claude Code itself is not modified.
| Where | What |
|---|---|
| Windows PC | Python 3 (python.org install, not the Microsoft Store alias), OpenSSH client (built into Windows 10/11) |
| Server | Linux, bash, curl, Claude Code, SSH access with key auth, sshd allowing stream-local forwarding (the default) |
mkdir -p ~/bin
cp remote/xclip ~/bin/xclip && chmod +x ~/bin/xclip~/bin must be on your PATH, ahead of /usr/bin if a real xclip is installed. Check with which xclip.
Then add Alt+V as an image-paste key. Windows Terminal swallows Ctrl+V; see Troubleshooting.
cp remote/keybindings.json ~/.claude/keybindings.json # or merge into your existing fileCopy clipserve.py and windows/clip-tunnel.bat to your user folder, e.g. %USERPROFILE%.
SSH config. Merge windows/ssh_config.example into %USERPROFILE%\.ssh\config, filling in your host, user and key. Then set HOST= at the top of clip-tunnel.bat to your normal alias.
Find your real pythonw.exe. Ignore anything under WindowsApps; that's the Store placeholder.
where.exe pythonwCreate two logon tasks. Run these in PowerShell and use the path from where.exe. Don't put \" quotes inside /tr; see Troubleshooting.
schtasks /create /f /tn "Clipboard bridge" /sc onlogon /tr "C:\Path\To\Python\pythonw.exe C:\Users\<you>\clipserve.py"
schtasks /create /f /tn "Clipboard tunnel" /sc onlogon /tr "conhost.exe --headless C:\Users\<you>\clip-tunnel.bat"Remove the default 72-hour run limit that schtasks puts on every task:
foreach ($t in 'Clipboard bridge','Clipboard tunnel') {
$s = (Get-ScheduledTask $t).Settings; $s.ExecutionTimeLimit = 'PT0S'
Set-ScheduledTask -TaskName $t -Settings $s | Out-Null
}Start both now, so you don't have to log out:
schtasks /run /tn "Clipboard bridge"
schtasks /run /tn "Clipboard tunnel"If your SSH key has a passphrase, the hidden tunnel can't prompt for it. Start the OpenSSH Authentication Agent service and ssh-add your key once.
On the server:
ls -l ~/.clip.sock # srw------- … your user
xclip -selection clipboard -t TARGETS -o # take a screenshot first → image/pngThen press Alt+V in claude.
After setup, there's nothing to do. Both tasks start hidden at logon, and the tunnel reconnects about 10 seconds after a network drop or sleep. Alt+V works in every claude session on the server, from any terminal: Windows Terminal, VS Code's integrated terminal, tmux, PuTTY.
These are the problems we ran into, in order, while getting this working.
Symptom. You press Ctrl+V in claude and nothing happens. No image, no error.
Diagnosis. We added a temporary log line to the shim (echo "$(date +%T) $*" >> ~/.cache/xclip-shim.log) and pressed Ctrl+V. The log stayed empty, so Claude Code never even tried to read the clipboard. tmux wasn't the culprit either: tmux list-keys | grep C-v showed it only binds C-v in copy mode.
Cause. Windows Terminal binds Ctrl+V to its own paste. With only an image on the clipboard, it pastes nothing and never sends the keystroke over SSH. VS Code's integrated terminal does the same. Claude Code on Linux binds image paste to Ctrl+V; on native Windows it uses Alt+V instead, for exactly this reason.
Fix. Bind Alt+V to chat:imagePaste in ~/.claude/keybindings.json on the server (remote/keybindings.json). Terminals pass Alt+V through. This is the fix we verified.
Alternatively, unbind Ctrl+V in your terminal (we didn't test these):
- Windows Terminal
settings.json→"actions":{ "command": "unbound", "keys": "ctrl+v" }, then open a new tab. - VS Code
keybindings.json:{ "key": "ctrl+v", "command": "-workbench.action.terminal.paste", "when": "terminalFocus" }
Cause. The first version of /targets read the full image and the text to decide what was on the clipboard. On Windows each read starts a PowerShell process, so it took 2–3 seconds, and the shim's curl --max-time 2 gave up first.
Fix. /targets now runs one PowerShell probe (Clipboard::ContainsImage() / ContainsText()) without reading any data, and the shim allows 5 seconds. A typical check now takes about 1 second.
Problem. The first design forwarded ssh -R 8765:127.0.0.1:8765, which opens a port on the server's 127.0.0.1:8765. On a shared server, any local user can then run curl localhost:8765/text and read your clipboard, passwords included. Worse, if they bind 8765 first, your paste returns their clipboard.
Fix. Forward to a unix socket instead: RemoteForward /home/<user>/.clip.sock 127.0.0.1:8765. sshd creates it with mode 0600 (StreamLocalBindMask defaults to 0177), so only your user can connect. The shim uses curl --unix-socket.
Problem. Only one SSH connection can own the socket. If you put RemoteForward on your everyday host alias, the first terminal you open owns it. Every later one warns that remote port forwarding failed, and paste dies everywhere once you close that first terminal.
Fix. Use a separate tunnel-only host alias (myserver-clip) held open by clip-tunnel.bat, and leave the forward off the alias your terminals and VS Code use. Every claude session on the server then shares the one socket.
Cause. sshd doesn't remove a stale socket file by default (StreamLocalBindUnlink no). When the old connection dies uncleanly, the file stays behind, and the new RemoteForward fails to bind. ExitOnForwardFailure yes makes that fail loudly instead of leaving you with a silently broken tunnel.
Fix. clip-tunnel.bat runs rm -f ~/.clip.sock over SSH before each connect, then retries every 10 seconds.
Meaning. The tunnel works: the socket exists and the request reached your PC. But nothing on your PC is listening on 127.0.0.1:8765, so ssh closed the channel. A delay of about 2 seconds before the error is the giveaway, because that's how long Windows takes to refuse a connection to a localhost port nobody is listening on.
Cause in our case. The "Clipboard bridge" task ran bare pythonw, which wasn't on the task's PATH, so it never started.
Fix. Run python clipserve.py in a window to confirm it starts, then recreate the task with the full path to pythonw.exe.
Meaning. clipserve is running, but there's no image on the clipboard. In our case, copying the test command into the terminal had replaced the screenshot. Take the screenshot last.
Copying an image file in Explorer doesn't count either; that puts a file reference on the clipboard, not image data. Open the image and copy it, or take a screenshot.
Cause. Under pythonw.exe (no console), sys.stderr is None. The HTTP server logs every request to stderr, so the first request raised AttributeError and the response failed.
Fix. log_message skips logging when sys.stderr is None.
Cause. \" only escapes quotes in cmd.exe. PowerShell passes it through differently, and schtasks receives a mangled /tr value.
Fix. If your paths have no spaces, drop the inner quotes:
/tr "C:\Path\pythonw.exe C:\Users\you\clipserve.py". If they do have spaces, run the command from cmd.exe instead.
Cause. schtasks /create sets "Stop the task if it runs longer than 3 days" by default.
Fix. The ExecutionTimeLimit = 'PT0S' snippet in the setup section.
Cause. Windows services run in Session 0, which is isolated from your desktop. The clipboard belongs to your logon session, so a clipserve service would run happily and always report an empty clipboard.
Fix. Use a scheduled task that runs at logon in your own session, as in this guide.
| Check | Command | Good result |
|---|---|---|
| Tunnel up? | ls -l ~/.clip.sock (server) |
srw------- |
| PC answering? | curl.exe -s http://127.0.0.1:8765/targets (PC) |
image/png after a screenshot |
| End to end | xclip -selection clipboard -t TARGETS -o (server) |
image/png |
| Shim being called? | temporary log line in ~/bin/xclip |
an entry each time you press Alt+V |
- One direction only: from your PC to the server. Copying into your PC's clipboard from the server isn't supported.
- No automatic restart: if
clipservecrashes, it stays down until your next logon, or until you runschtasks /run /tn "Clipboard bridge". - One tunnel per server user: each user on a shared server needs their own tunnel and shim.
- Not needed in the VS Code Claude Code panel: that panel runs on your PC and pastes images natively.
- On your PC,
clipservelistens on127.0.0.1only. Any process running on your PC can read your clipboard through it, which is no worse than local programs reading the clipboard directly. - On the server, the socket is
0600inside your home directory, so other users can't reach it. - It only serves reads, and only while the tunnel is up.
Why can't I paste images into Claude Code over SSH?
On Linux, Claude Code reads clipboard images with xclip (or wl-paste). Both need a graphical display, and a headless SSH server has none, so there's no clipboard to read. Your screenshot lives on your local PC, not on the server.
Do I need to install anything on the server?
No packages. Just bash, curl and the xclip shim from this repo. You don't need X11, Wayland, X forwarding or a real xclip.
Does it work with VS Code Remote-SSH, tmux, or Windows Terminal?
Yes. It works in any terminal that connects to the server over SSH, including VS Code's integrated terminal, tmux, Windows Terminal and PuTTY. Every claude session on the server shares the same tunnel. The Claude Code panel in VS Code doesn't need it, since that panel runs on your PC.
Why Alt+V instead of Ctrl+V? Windows Terminal and VS Code catch Ctrl+V for their own text paste, so the key never reaches Claude Code when the clipboard holds an image. Alt+V passes through. You can still free up Ctrl+V in your terminal's settings (see Troubleshooting).
Does it work from macOS or a Linux desktop?
clipserve.py also has macOS (osascript) and Linux desktop (xclip/wl-paste) code. Windows is the setup we tested end to end and documented here.
Is it safe on a shared server?
Yes. The tunnel ends in a unix socket with mode 0600 in your home directory, so other users on the server can't read your clipboard. Avoid forwarding a plain TCP port (ssh -R 8765:…); every local user can reach one of those.
Can I copy from the server back into my Windows clipboard? No. It only serves reads, from your PC to the server.
Does it need X11 forwarding (ssh -X)?
No. It uses a plain SSH reverse tunnel, so no X server is needed on either side.
MIT