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
- Default or otherwise valid
~/.qwen/settings.json.
- Take a screenshot (Cmd+Shift+4) so the pasteboard holds a real PNG.
- 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. saveClipboardImage → ensureClipboardDir 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/tmp → os.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.
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
main(reproduced on v0.14.5)Steps to reproduce
~/.qwen/settings.json.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.5native binding is broken on darwin-arm64clipboardHasImage()atpackages/cli/src/ui/utils/clipboardUtils.tscallsnew ClipboardManager().hasFormat('image'). On darwin-arm64 this returnsfalseeven when the pasteboard holds a real image.Direct Node verification against the same installed native module:
Because
hasFormat('image')returns false,handleClipboardImageearly-exits and no attachment is produced. This is the primary reason Cmd+V feels unimplemented on macOS arm64.Root cause B — invalid
advanced.runtimeOutputDirsilently breaks all runtime outputStorage.getGlobalTempDir()returns the configuredruntimeOutputDirverbatim with no existence check.saveClipboardImage→ensureClipboardDircallsfs.mkdir('<path>/clipboard', { recursive: true }), which throwsENOENT: no such file or directorywhenever 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:debugLogger.erroris 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 ofStorage.getRuntimeBaseDir()(debug logs, session history, todos, insights).Why this deserves fixing upstream
runtimeOutputDiris documented as user-configurable; a stale value should not disable unrelated features across the whole session.Proposed fixes
osascript(converts PNGf/JPEG/TIFF from the pasteboard to file bytes)xclip -selection clipboard -t image/png -o/wl-paste --type image/png[System.Windows.Forms.Clipboard]::GetImage()Verified end-to-end on darwin-arm64:
osascriptround-trips a PNG from clipboard to disk with bytes identical to the source.ensureClipboardDirrobust. Try a candidate chain —targetDir→~/.qwen/tmp→os.tmpdir()→process.cwd()— and use the first one thatmkdiraccepts. Signature unchanged; existing callers unaffected when the primary path works.debugLogger.errorin the clipboard catch blocks with a short UI error chip so users can self-diagnose.