[BUG] WSL/WSLg: image paste fails because wl-paste returns BMP into a .png path

Status Open
Reported on v2.1.241
Maintainer reply None cached
Activity 4 comments · opened Aug 24, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

On WSL2 with WSLg, pasting a Windows screenshot (ctrl+v or alt+v) always answers No image found in clipboard. Use ctrl+v to paste images.

The detection step is not the problem — it succeeds. checkImage falls back from xclip -t TARGETS to wl-paste -l, which reports image/bmp, and that matches the regex.

The failure is the format. saveImage runs its chain and the winning link is:

wl-paste --type image/bmp > <tmp>/claude_cli_latest_screenshot.png

WSLg publishes the Windows clipboard image only as image/bmp, so the file written to a .png path holds a BMP. It is rejected downstream, and the user sees No image found in clipboard.

The PowerShell fallback appended for WSL — which would have produced a real PNG — never runs, because the BMP link exited 0 before it was reached.

What Should Happen?

The screenshot should attach, as it does on other platforms.

Either fix would do:

  1. Convert on read — transcode to PNG when the winning link produced a BMP.
  2. Reorder for WSL — try the PowerShell PNG path before the image/bmp links when the platform is wsl, since it yields a directly usable PNG.

Error Messages/Logs

$ wl-paste -l
image/bmp

$ wl-paste --type image/png > /tmp/x 2>/dev/null; echo "exit=$?"
exit=1

$ wl-paste --type image/bmp > /tmp/x; file -b /tmp/x
PC bitmap, Windows 3.x format, 1920 x 1032 x 32

$ xclip -selection clipboard -t TARGETS -o
TIMESTAMP
TARGETS
UTF8_STRING
TEXT

# the data IS available as PNG on the Windows side, just not over Wayland:
$ powershell.exe -NoProfile -Command "...GetDataObject().GetFormats()"
System.Drawing.Bitmap, Bitmap, PNG, CanUploadToCloudClipboard, CanIncludeInClipboardHistory

Steps to Reproduce

  1. Run Claude Code inside WSL2 with WSLg, with wl-clipboard installed.
  2. On the Windows side, take a screenshot with Win+Shift+S so it lands on the Windows clipboard.
  3. At the Claude Code prompt, press ctrl+v (or alt+v).
  4. No image found in clipboard. Use ctrl+v to paste images. appears and nothing is attached.

Confirmation that only the format is at fault — put a real PNG on the Wayland clipboard and paste again:

wl-copy --type image/png < some.png

Both ctrl+v and alt+v then attach the image immediately, with no other change.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.241 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Windows Terminal

Additional Information

This replaces my original description of the same report, which had the cause wrong. I first blamed checkImage for lacking a wl-paste fallback; it has one, the chain is fine, and detection succeeds. The defect is that a BMP ends up in a file named .png.

Environment: WSL2 (kernel 6.18.33.2-microsoft-standard-WSL2), Ubuntu, wl-clipboard 2.2.1, xclip 0.13, Windows Terminal, WSLg active (WAYLAND_DISPLAY=wayland-0, DISPLAY=:0).

Feedback ID from the in-app report: fe517440-cbac-44ee-b945-56e51653bcfe

Workaround in use: a local wl-paste wrapper that serves image/png by transcoding the BMP with ffmpeg (~500 ms, against ~1.7 s for the PowerShell path) and advertises image/png in --list-types. Paste works normally with it in place.

View original on GitHub ↗

3 Comments

igorgnip-gmail · 1 month ago

Claude actually found:
Confirmed the root cause. Look at the BMP header from your clipboard screenshot:

PC bitmap, Windows 3.x format, 124 x 78 x 32, 3 compression, image size 38688...

3 compression = BI_BITFIELDS. WSLg only ever forwards Windows→Linux clipboard images as image/bmp (confirmed above — wl-paste -l offers only image/bmp, no image/png), and Windows encodes them with BI_BITFIELDS compression. Claude Code's clipboard-image decoder doesn't handle that BMP variant, so it grabs the bytes, fails to decode, and silently drops it — same failure whether triggered by Ctrl+V or Alt+V, since both funnel into the same decode step.

This used to work before I updated claude this morning - but I also updated wsl2 at the same time so I am not sure if it is claude regression or wsl2 changed BMP format.

igorgnip-gmail · 1 month ago

Confirming this on Claude Code 2.1.263 / WSL2 (Ubuntu, WSLg) — same root cause as above: wl-paste -l only ever offers image/bmp, and the BMP is BI_BITFIELDS-compressed, which the downstream decoder rejects.

This is a client-side workaround only, to unblock people until Claude Code fixes the decoding/format-selection upstream — it doesn't touch Claude Code itself. Here's a workaround that fixes it a different way: instead of wrapping wl-paste, a small background daemon watches the actual Wayland clipboard and republishes the image as a real image/png entry whenever a BMP-only image lands on it (via ImageMagick, convert/magick handles BI_BITFIELDS fine). Once that PNG entry exists, Claude Code's existing wl-paste --type image/png path just works — no changes needed on the Claude Code side, and normal text copy/paste is untouched.

Note: wl-paste --watch doesn't work under WSLg (Watch mode requires a compositor that supports the wlroots data-control protocol), so this polls once a second instead.

Requirements: sudo apt-get install -y wl-clipboard imagemagick

~/.local/bin/wsl-clip-image-bridge-tick.sh (one poll tick):

#!/usr/bin/env bash
# One tick of the clipboard bridge. WSLg forwards Windows clipboard images
# as BI_BITFIELDS-compressed BMP, which Claude Code's image-paste decoder
# can't read. This converts to PNG and republishes on the clipboard.
set -euo pipefail

STATE_DIR="${XDG_RUNTIME_DIR:-/tmp}/wsl-clip-image-bridge"
LAST_HASH_FILE="$STATE_DIR/last.hash"
mkdir -p "$STATE_DIR"

types=$(wl-paste -l 2>/dev/null || true)

case "$types" in
  *image/bmp*)
    if echo "$types" | grep -q "image/png"; then
      exit 0
    fi
    bmp="$(mktemp --suffix=.bmp)"
    png="$(mktemp --suffix=.png)"
    trap 'rm -f "$bmp" "$png"' EXIT

    wl-paste -t image/bmp > "$bmp" 2>/dev/null || exit 0

    hash=$(sha256sum "$bmp" | cut -d' ' -f1)
    if [ "$hash" = "$(cat "$LAST_HASH_FILE" 2>/dev/null || true)" ]; then
      exit 0
    fi
    echo "$hash" > "$LAST_HASH_FILE"

    if magick "$bmp" "$png" 2>/dev/null; then
      wl-copy -t image/png < "$png"
    fi
    ;;
esac

~/.local/bin/wsl-clip-image-bridge.sh (poll loop):

#!/usr/bin/env bash
# wl-paste --watch needs the wlroots data-control protocol, which WSLg's
# compositor doesn't implement, so we poll instead.
set -euo pipefail
TICK="$(dirname "$0")/wsl-clip-image-bridge-tick.sh"
while true; do
    "$TICK" || true
    sleep 1
done
chmod +x ~/.local/bin/wsl-clip-image-bridge.sh ~/.local/bin/wsl-clip-image-bridge-tick.sh

Autostart — add to ~/.bashrc (or ~/.zshrc):

if command -v wl-paste >/dev/null 2>&1 && ! pgrep -f wsl-clip-image-bridge.sh >/dev/null 2>&1; then
    nohup "$HOME/.local/bin/wsl-clip-image-bridge.sh" >/tmp/wsl-clip-image-bridge.log 2>&1 &
    disown
fi

Open a new shell (or run that block manually once) and normal ctrl+v/alt+v paste starts working immediately — confirmed working here. Obviously just a stopgap; the real fix belongs in Claude Code's BMP decoding (or preferring the PowerShell PNG path on WSL, as suggested above).

igorgnip-gmail · 1 month ago

Follow-up for anyone who applied the wsl-clip-image-bridge workaround above: once a Claude Code release actually fixes this upstream (BMP decoding or the PowerShell PNG path), here's how to remove it cleanly. It's safe to leave running indefinitely too (it's a no-op once Claude Code stops needing it), but tidy to remove.

1. Stop the running daemon:

pkill -f wsl-clip-image-bridge.sh

2. Remove the autostart block from ~/.bashrc. It's the block that starts with the comment # WSLg only forwards Windows clipboard images... and ends with the closing fi right after the nohup .../wsl-clip-image-bridge.sh ... line — delete these exact lines (7 lines total):

# WSLg only forwards Windows clipboard images as BI_BITFIELDS BMP, which
# Claude Code's image-paste decoder can't read (Ctrl+V/Alt+V show
# "Pasting..." but nothing attaches). This bridges each clipboard image to
# a plain image/png entry. Safe to run on every shell: no-ops if already
# running.
if command -v wl-paste >/dev/null 2>&1 && ! pgrep -f wsl-clip-image-bridge.sh >/dev/null 2>&1; then
    nohup "$HOME/.local/bin/wsl-clip-image-bridge.sh" >/tmp/wsl-clip-image-bridge.log 2>&1 &
    disown
fi

(If you added it to ~/.zshrc instead, remove the same block from there.)

3. Delete the scripts and leftover state:

rm -f ~/.local/bin/wsl-clip-image-bridge.sh ~/.local/bin/wsl-clip-image-bridge-tick.sh
rm -f /tmp/wsl-clip-image-bridge.log
rm -rf "${XDG_RUNTIME_DIR:-/tmp}/wsl-clip-image-bridge"

That's it — no other state or config is touched by this workaround, so those three steps fully remove it. wl-clipboard and imagemagick (installed as prerequisites) are safe to leave installed or remove with your package manager if you don't need them for anything else.

Showing cached comments. Read the full discussion on GitHub ↗