Skip to content

Clipboard image paste (Cmd+V) silently fails on macOS — two root causes #3517

@callmeYe

Description

@callmeYe

Summary

On macOS, pasting an image into the prompt does nothing — no attachment, no reference inserted into the buffer, no visible error. Two independent root causes combine to make Cmd+V image paste look like an unimplemented feature.

Environment

  • macOS 14 (Darwin 24.x) arm64
  • Node 22.17
  • qwen-code main (reproduced on v0.14.5)
  • Terminal.app / iTerm2 with bracketed paste enabled

Steps to reproduce

  1. Default or otherwise valid ~/.qwen/settings.json.
  2. Take a screenshot (Cmd+Shift+4) so the pasteboard holds a real PNG.
  3. Start qwen, press Cmd+V in the input prompt.

Expected: image attached, reference inserted in the message.

Actual: nothing happens. Looks like the feature doesn't exist.

Root cause A — @teddyzhu/clipboard@0.0.5 native binding is broken on darwin-arm64

clipboardHasImage() at packages/cli/src/ui/utils/clipboardUtils.ts calls new ClipboardManager().hasFormat('image'). On darwin-arm64 this returns false even when the pasteboard holds a real image.

Direct Node verification against the same installed native module:

# Clipboard currently has a macOS screenshot
$ osascript -e 'clipboard info'
«class PNGf», 158517, «class 8BPS», 799244, GIF picture, 74458,
«class jp2 », 167307, JPEG picture, 114142, TIFF picture, 2770286, ...

$ node -e '(async () => {
    const m = await import("@teddyzhu/clipboard");
    const c = new m.ClipboardManager();
    console.log("hasFormat(image):", c.hasFormat("image"));
    try { console.log(m.getClipboardImage()); } catch (e) { console.log("throw:", e.message); }
  })()'
hasFormat(image): false
throw: Failed to get image: no image data

Because hasFormat('image') returns false, handleClipboardImage early-exits and no attachment is produced. This is the primary reason Cmd+V feels unimplemented on macOS arm64.

Root cause B — invalid advanced.runtimeOutputDir silently breaks all runtime output

Storage.getGlobalTempDir() returns the configured runtimeOutputDir verbatim with no existence check. saveClipboardImageensureClipboardDir calls fs.mkdir('<path>/clipboard', { recursive: true }), which throws ENOENT: no such file or directory whenever an ancestor of the configured path does not exist on disk (stale value synced across machines, typo, removed mount, etc.). The caller wraps everything in:

try { /* ... */ } catch (error) {
  debugLogger.error('Error handling clipboard image:', error);
}

debugLogger.error is not surfaced in the UI, so the user sees a silent no-op and has no signal at all about the underlying write failure. The same silent failure hits every consumer of Storage.getRuntimeBaseDir() (debug logs, session history, todos, insights).

Why this deserves fixing upstream

  • runtimeOutputDir is documented as user-configurable; a stale value should not disable unrelated features across the whole session.
  • The native-addon-based clipboard read has been broken on at least one supported platform since v0.14.5 with no graceful degradation.
  • Silent no-ops make it impossible for users to diagnose whether the feature is supported, whether their terminal is at fault, or whether the clipboard even has an image.

Proposed fixes

  • Drop the native addon. Use platform-native shell commands that need no compile step and are provably reliable:
    • darwin: osascript (converts PNGf/JPEG/TIFF from the pasteboard to file bytes)
    • linux: xclip -selection clipboard -t image/png -o / wl-paste --type image/png
    • win32: PowerShell [System.Windows.Forms.Clipboard]::GetImage()
      Verified end-to-end on darwin-arm64: osascript round-trips a PNG from clipboard to disk with bytes identical to the source.
  • Make ensureClipboardDir robust. Try a candidate chain — targetDir~/.qwen/tmpos.tmpdir()process.cwd() — and use the first one that mkdir accepts. Signature unchanged; existing callers unaffected when the primary path works.
  • (Follow-up, optional) Replace silent debugLogger.error in the clipboard catch blocks with a short UI error chip so users can self-diagnose.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions