Skip to content
rohithpadmanabhuniPublic

About

Paste images and screenshots from your Windows clipboard into Claude Code running on a remote Linux server over SSH. Fixes image paste / Ctrl+V not working in headless SSH, tmux and VS Code Remote-SSH sessions.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

clipserve: paste images into Claude Code over SSH (Windows → remote Linux)

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) from xclip
  • 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.

How it works

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.py is 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/xclip is a bash shim that turns Claude Code's xclip calls into curl requests over that socket.

Claude Code itself is not modified.

Requirements

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)

Setup

1. Server side

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 file

2. Windows side

Copy 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 pythonw

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

3. Test

On the server:

ls -l ~/.clip.sock                                  # srw------- … your user
xclip -selection clipboard -t TARGETS -o            # take a screenshot first → image/png

Then press Alt+V in claude.

Daily use

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.

Troubleshooting: every error we hit, and the fix

These are the problems we ran into, in order, while getting this working.

Ctrl+V does nothing (the shim is never called)

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" }

TARGETS timed out on Windows

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.

Security: a loopback TCP port is visible to every user on the server

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.

Several SSH sessions and VS Code at once

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.

Tunnel won't come back after sleep or a network drop

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.

curl: (52) Empty reply from server

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.

curl: (22) … 404 on /image

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.

clipserve works in a window but fails as a background task

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.

schtasks "ERROR: Invalid argument/option"

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.

Tasks silently stop after 3 days

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.

"Make it a Windows service" doesn't work

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.

Quick diagnostics

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

Limitations

  • 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 clipserve crashes, it stays down until your next logon, or until you run schtasks /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.

Security notes

  • On your PC, clipserve listens on 127.0.0.1 only. 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 0600 inside your home directory, so other users can't reach it.
  • It only serves reads, and only while the tunnel is up.

FAQ

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.

License

MIT

About

Paste images and screenshots from your Windows clipboard into Claude Code running on a remote Linux server over SSH. Fixes image paste / Ctrl+V not working in headless SSH, tmux and VS Code Remote-SSH sessions.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages