Fixing Image Copy-Paste in iTerm2 with Hammerspoon
Fixing Image Copy-Paste in iTerm2 with Hammerspoon
The problem
If you use iTerm2 on macOS, you’ve probably hit this: you copy an image (screenshot, diagram, anything visual), switch to iTerm2, hit Cmd+V… and nothing happens. No error, no feedback. Just silence.
This is because terminal emulators operate over a text-based PTY layer. The clipboard shortcut in a terminal only handles plain text — it has no mechanism to transmit binary image data into a running CLI app.
This becomes especially annoying when working with tools like Claude Code that accept image input. You end up having to save the image to a file manually, then type the path. Or drag and drop. Every single time.
What doesn’t work
- Cmd+V — only pastes text in iTerm2, images are silently ignored
- Ctrl+V — mapped for image paste in some CLI apps but unreliable (works ~50-60% of the time in iTerm2)
- iTerm2 settings — there is no native plugin system or profile setting that handles clipboard image paste
The solution: Hammerspoon
Hammerspoon is an open-source macOS automation tool that hooks into system APIs. The idea is simple: intercept Cmd+V when iTerm2 is focused, check if the clipboard contains an image, save it to /tmp, and type the file path instead.
Install Hammerspoon
brew install --cask hammerspoonLaunch it from Applications, then grant Accessibility access when prompted (System Settings > Privacy & Security > Accessibility).
First attempt (naive)
The initial config looked like this:
-- ~/.hammerspoon/init.luahs.hotkey.bind({"cmd"}, "V", function() local app = hs.application.frontmostApplication() if app:name() == "iTerm2" then local img = hs.pasteboard.readImage() if img then local path = "/tmp/clip_" .. os.time() .. ".png" img:saveToFile(path) hs.eventtap.keyStrokes(path) return end end -- fall through to normal paste hs.eventtap.keyStroke({"cmd"}, "V")end)Image paste worked. Copy a screenshot, Cmd+V in iTerm2, and the file path /tmp/clip_1774713509.png gets typed out automatically.
But there was a problem.
The bug: text paste broke
Regular text Cmd+V stopped working entirely. Nothing happened when pasting text — in iTerm2 or anywhere else.
The issue: hs.hotkey.bind captures Cmd+V globally. When the clipboard has no image, the fallback calls hs.eventtap.keyStroke({"cmd"}, "V") which triggers the same hotkey again, creating a recursive loop that gets swallowed.
The fix: disable before re-firing
The solution is to temporarily disable the hotkey before sending the real Cmd+V keystroke, then re-enable it:
-- ~/.hammerspoon/init.lualocal pasteHotkeypasteHotkey = hs.hotkey.bind({"cmd"}, "V", function() local app = hs.application.frontmostApplication() if app:name() == "iTerm2" then local img = hs.pasteboard.readImage() if img then local path = "/tmp/clip_" .. os.time() .. ".png" img:saveToFile(path) hs.eventtap.keyStrokes(path) return end end -- disable our hotkey, fire the real Cmd+V, then re-enable pasteHotkey:disable() hs.eventtap.keyStroke({"cmd"}, "V") pasteHotkey:enable()end)Key difference: storing the hotkey in a variable (pasteHotkey) lets us call :disable() and :enable() on it. The real Cmd+V fires while our hook is off, so no recursion.
Reload the config from the Hammerspoon menu bar icon, and both text and image paste work correctly.
How it works in practice
- Copy an image (screenshot, browser image, anything)
- Switch to iTerm2
- Hit Cmd+V
- Hammerspoon saves the image to
/tmp/clip_<timestamp>.png - The file path gets typed into the terminal automatically
For regular text, Cmd+V works exactly as before — in iTerm2 and every other app.
Alternative approaches
If you don’t want a background daemon, lighter options exist:
- pngpaste (
brew install pngpaste) — CLI tool that saves clipboard image to a file. Manual but no daemon. - osascript one-liner — uses AppleScript to read clipboard as PNG. Zero dependencies.
- Shell function wrapping either of the above, aliased to something short like
clipimg.
Hammerspoon is the only approach that makes Cmd+V “just work” without changing your workflow.