Pasting images into a remote coding agent over SSH¶
Status: research only — no solution adopted yet. This doc surveys the landscape for getting a local clipboard image (a screenshot) into a coding agent (Claude Code, and Neovim-adjacent workflows) that is running on a remote box over SSH. It is the image-shaped sequel to clipboard.md: OSC 52 solves the text half of remote clipboard sync, but it explicitly cannot carry image or file payloads, so a different mechanism is needed.
When we pick and wire something into the dotfiles, this doc graduates from "survey" to "here's how it's wired" (and gets a .zh-TW.md mirror like the rest of docs/tools/).
The core constraint¶
| Where it lives | How it crosses SSH today | |
|---|---|---|
| Yanked text | remote app → local clipboard | OSC 52 — escape sequence rides the TTY up to the local terminal (clipboard.md) |
| Screenshot image | local clipboard → remote agent | ❌ nothing built-in — OSC 52 is text-only; the image bytes never leave your laptop |
The asymmetry is the whole problem. The clipboard with the screenshot in it is on your local machine; the agent that needs to see the image runs on the remote box. OSC 52 goes the wrong direction (remote→local) and only carries base64 text anyway. So every solution below boils down to the same move: get the local image bytes onto the remote filesystem as a file, then hand the agent that file path (agents read images from paths via their Read tool). They differ only in how the bytes travel and how automated the round-trip is.
Why not just a terminal image protocol (kitty graphics / sixel / iTerm2 inline)? Those are output protocols — they let a remote program draw an image into your terminal. They do not give a remote program a way to ingest an image sitting in your local clipboard. Wrong direction again. (We already have
allow-passthrough onfor the display side; it does nothing for input.)
Taxonomy of approaches¶
0. Baseline — manual file + path reference (zero tooling)¶
Save the screenshot somewhere the remote can see it, then reference the path in the agent prompt.
scp ~/shot.png remote:/tmp/ # or drag into an sshfs/VS Code mount
# then in Claude Code on the remote:
# @/tmp/shot.png what's wrong with this layout?
- Pros: works everywhere, no daemon, no tunnel, survives tmux/mosh. This is the fallback that always works.
- Cons: manual every time; breaks flow. Everything below is just automation of this baseline.
1. Networked daemon + SSH reverse tunnel (the Zeitler family)¶
The most principled fit for a pure terminal / tmux workflow. A tiny daemon runs on your local machine and reads the local clipboard; the remote side reaches it through an SSH reverse tunnel (RemoteForward) and writes the bytes to a temp file. We already keep long-lived SSH configs, so adding a RemoteForward line is cheap.
1a. claude-ssh-image-skill — ccimgd + ccimg + /paste-image skill¶
AlexZeitler/claude-ssh-image-skill · MIT · Go (static binaries) · the direct answer to the original question.
Architecture (base64-over-TCP, no scp):
/paste-image (remote Claude skill)
└─ Bash: ccimg (remote client)
└─ TCP 127.0.0.1:9998 ──(SSH -R reverse tunnel)──▶ ccimgd (local daemon)
└─ reads local clipboard
(wl-paste / xclip / pngpaste)
◀── JSON { ok, image: <base64 PNG> } ───────────────────
└─ ccimg writes /tmp/…png, prints the path
└─ Claude's Read tool loads that path → image is now in the session
Setup, verbatim from the README:
# build both binaries (needs Go)
git clone https://github.com/AlexZeitler/claude-ssh-image-skill.git
cd claude-ssh-image-skill && ./build.sh
# local daemon — Linux
cp daemon/ccimgd-linux-amd64 ~/.local/bin/ccimgd
cp daemon/ccimgd.service ~/.config/systemd/user/
systemctl --user daemon-reload && systemctl --user enable --now ccimgd
# local daemon — macOS
brew install pngpaste
cp daemon/ccimgd-darwin-arm64 /usr/local/bin/ccimgd
cp daemon/com.ccimgd.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.ccimgd.plist
# remote: client binary + the skill
scp client/ccimg-linux-amd64 remote:~/.local/bin/ccimg
scp skill/paste-image.md remote:~/.claude/commands/paste-image.md
Reverse tunnel (in ~/.ssh/config so it's automatic):
…or ad hoc: ssh -R 9998:localhost:9998 remote-server. Allow the client without a prompt by adding "Bash(ccimg)" to the remote ~/.claude/settings.json permissions.allow. Usage: copy an image locally, then run /paste-image in the remote Claude session.
- Clipboard backends: Wayland
wl-paste, X11xclip, macOSpngpaste(auto-detected). - Fit for us: ★ best fit for terminal/tmux. Port 9998 is deliberately distinct from sshimg.nvim's 9999 so both daemons can run at once.
- Cost: must keep the reverse tunnel up; local daemon must be running; PNG-only.
1b. sshimg.nvim — imgd + Neovim plugin (scp transfer)¶
AlexZeitler/sshimg.nvim · MIT · Python daemon + Lua plugin. Same author, Neovim target instead of Claude, and a different transport: scp rather than base64-over-TCP.
# local daemon
cp daemon/imgd.py ~/.local/bin/imgd && chmod +x ~/.local/bin/imgd
cp daemon/imgd.service ~/.config/systemd/user/imgd.service
systemctl --user enable --now imgd
ssh -R 9999:localhost:9999 yourserver # reverse tunnel (port 9999)
-- lazy.nvim
{ "AlexZeitler/sshimg.nvim", opts = {
port = 9999, host = "127.0.0.1",
-- <leader>pa → save under ./assets/ ; <leader>pp → save beside current file
} }
Press <leader>pa / <leader>pp in a Markdown buffer on the remote → the plugin scps the local clipboard image over and inserts  at the cursor.
- Deps: local =
wl-paste+scp+ Python 3 (Wayland only, per README); remote = Neovim + Python 3. - Fit for us: ★ complements our Neovim-over-SSH setup (the SSH-conditional OSC 52 provider in
clipboard.md§3 "Editor — Neovim"). It writes a real file into the repo tree, which is exactly what an agent reading that Markdown later would want. Downsides: Wayland-only clipboard read as documented; no macOS/X11 path out of the box.
Background article tying 1a+1b together: Paste clipboard images into Claude Code over SSH.
2. GUI-editor bridge — let the editor own the clipboard¶
If the agent is driven from VS Code Remote-SSH, the paste problem disappears: VS Code runs locally and owns the local clipboard, so a normal Ctrl/Cmd+V into the Claude Code extension's input carries the image through the editor's remote channel — no daemon, no tunnel.
- Official: Claude Code VS Code extension over Remote-SSH. Zero extra setup beyond the two extensions; also gives graphical diffs/plan review.
- Community CLI-in-terminal variants (for when you run the Claude CLI inside VS Code's integrated terminal rather than the extension): e.g. marcucio/claude-image-paste reads the local clipboard, uploads via the Remote-SSH filesystem, and pastes the resulting remote path. Marketplace has a few more (
claude-remote-image-paste,claude-paste-image) doing the same trick. - Fit for us: ★ best fit for the VS Code side of a dual workflow. Does nothing for a bare tmux SSH session.
3. Local clipboard → path injector (terminal-agnostic)¶
Tools that hotkey-capture or read the local clipboard, drop it to a file, and type the file path into whatever terminal has focus. Because they inject text (a path), they sail over SSH like any keystrokes.
| Tool | Platform | Mechanism | Notes |
|---|---|---|---|
| Invoke | cross-platform | hotkey screenshot → ~/.screenshots/… → pastes the path |
paid (~$49 one-time); zero SSH config; but path is local — only works if the terminal app maps it to a remote-visible path (mount) or you accept a local capture flow |
| Clipport | macOS + iTerm2 | clipportd daemon uploads the clipboard image over the existing SSH connection, inserts the remote path |
single keystroke; iTerm2 paste hotkey → helper; macOS/iTerm2 only, proprietary protocol (not OSC 52) |
- Fit for us: situational. Clipport is the cleanest single-keystroke UX if you standardize on macOS + iTerm2 (we don't — Ghostty/Alacritty across macOS + Linux).
4. Cloud — sidestep SSH entirely¶
Claude Code on the web runs the agent in Anthropic's sandbox; you paste images straight into the browser (local clipboard, no SSH in the loop).
- Fit for us: only when the code can live in Anthropic's cloud. Not a general answer for arbitrary remote boxes / private infra.
Comparison matrix¶
| Approach | Target | Local deps | Remote deps | Transport | Tunnel? | Platforms | Fit |
|---|---|---|---|---|---|---|---|
0 · manual scp + @path |
any agent | — | — | scp/mount | no | all | fallback |
| 1a · claude-ssh-image-skill | Claude Code CLI | ccimgd + wl-paste/xclip/pngpaste | ccimg + skill | base64/TCP :9998 | reverse | Wayland/X11/macOS | ★ terminal |
| 1b · sshimg.nvim | Neovim | imgd + wl-paste + scp + py3 | nvim + py3 | scp :9999 | reverse | Wayland (docs) | ★ nvim |
| 2 · VS Code Remote-SSH | Claude ext / CLI | VS Code | Remote-SSH server | editor channel | no | all | ★ VS Code |
| 3 · Invoke / Clipport | any terminal agent | daemon | — | path inject / upload | no | Invoke: all · Clipport: mac+iTerm2 | situational |
| 4 · Claude Code web | web agent | browser | — (cloud) | HTTPS | no | all | cloud-only |
Fit against this setup (notes for the eventual decision)¶
- We are dual-mode (owner uses both tmux/terminal and VS Code/Neovim), so the likely answer is two complementary picks, not one: 1a for terminal/tmux Claude, and either 2 (VS Code) or 1b (Neovim) for the editor side. 1a and 1b coexist by design (ports 9998 vs 9999).
- Reverse tunnel is the recurring cost. We already curate
~/.ssh/config; aRemoteForwardline per box is the price of entry for the whole family-1 approach. Worth deciding whether to standardize a port (e.g. 9998) across hosts. - Clipboard backend spread: our machines span macOS, Wayland, and X11. 1a auto-detects all three; 1b as documented is Wayland-only — a gap to weigh if the owner is on macOS/X11 for the Neovim path.
- Existing infra already helps the display side (
allow-passthrough on,set-clipboard on) but is orthogonal to input; none of it needs to change. - Build/trust: family-1 tools are small MIT Go/Python we'd build from source and run as a user systemd/launchd service reading the clipboard and listening on localhost — same trust surface as the
xCLI. No root. - Security note: a localhost daemon that serves your clipboard image on demand is reachable by anything that can hit the forwarded port on the remote — i.e. anyone with an account on that box while the tunnel is up. Acceptable for single-user boxes; think twice on shared hosts.
Open questions before adopting¶
- One tool or two (terminal + editor)? Standardize the reverse-tunnel port?
- Package via ansible
devtoolsrole (build Go binary + install user service) or vendor prebuilt binaries? - macOS/X11 story for the Neovim path if we want 1b (upstream is Wayland-only) — patch or restrict to Claude-CLI (1a) only?
- Wrap the reverse-tunnel
RemoteForwardinto the managed SSH config, or leave it host-local?
External references¶
- claude-ssh-image-skill (ccimgd/ccimg) — MIT
- sshimg.nvim — MIT
- Article: Paste clipboard images into Claude Code over SSH
- Article: Introducing sshimg.nvim
- Clipport · write-up
- Invoke — screenshot paste into the terminal
- Claude Code in VS Code · Claude Code on the web
Related docs¶
clipboard.md— OSC 52 text clipboard sync (the text half; explains why images can't ride OSC 52)tmux/README.md—allow-passthrough/ terminal image display sideaicapture.md— the text scrollback → agent capture layer (adjacent "feed context to the agent" tooling)