`sharp` moved to devDependencies in 0.23.0 — all image support silently broken for installed users
Summary
sharp is a runtime dependency but has been declared only in devDependencies since 0.23.0. It is therefore never installed for anyone who installs the published package, and every image code path fails. The failure is silent and reported as a wrong, misleading message.
This is a regression of the fix in #308 (which closed #303 "can't paste image").
Evidence
Straight from the registry:
$ npm view @gitlawb/openclaude@<v> dependencies.sharp optionalDependencies.sharp devDependencies.sharp
0.21.0 dep=^0.34.5 optional=— dev=—
0.22.0 dep=^0.34.5 optional=— dev=—
0.23.0 dep=— optional=— dev=^0.34.5 <-- moved here
0.24.0 dep=— optional=— dev=^0.34.5
0.30.0 dep=— optional=— dev=^0.34.5allowScripts still carries "[email protected]": true, so the native install step is still expected — only the declaration that would trigger it is gone.
Why it is not caught in development
In a checkout, devDependencies are installed, so sharp resolves and everything works. Only a clean install of the published tarball reproduces it.
Mechanism
getImageProcessor() has two sources:
- the native
image-processor-napimodule — guarded byisInBundledMode(), which requirestypeof Bun !== "undefined"and a non-emptyBun.embeddedFiles.bin/openclaudeis#!/usr/bin/env node, so under a normal npm install this branch never runs. (In the published bundle that module is a proxy stub with__stub = trueanyway.) await import("sharp")— throwsERR_MODULE_NOT_FOUND, sogetImageProcessor()throwsImageProcessorUnavailableError.
maybeResizeAndDownsampleImageBuffer() calls getImageProcessor() as its first statement, unconditionally — before any size or dimension check. So it throws for every image, including ones small enough to need no processing at all.
getImageFromClipboard() wraps the whole body in catch { return null } (src/utils/imagePaste.ts), which swallows that error. PromptInput sees null and shows:
No image found in clipboard. Use ctrl+v to paste images.
The clipboard read itself succeeded. osascript … «class PNGf» exits 0 and writes a valid PNG to /tmp/claude_cli_latest_screenshot.png (verified: 2064×1096, 817 KB). The failure is entirely in the processing step, and the message points the user at the wrong thing.
Affected paths
- clipboard image paste (
getImageFromClipboard) - drag-and-drop / pasted image paths (
tryReadImageFromPath) - reading image files with the file-read tool
- BMP→PNG conversion for WSL2
Reproduce
npm install -g @gitlawb/[email protected]
node -e 'import("sharp").catch(e => console.log(e.code))' # ERR_MODULE_NOT_FOUND
# copy any image, start openclaude, press ctrl+v
# -> "No image found in clipboard"Environment
macOS 15 (Darwin 25.2.0), Node v22.17.0 via nvm, @gitlawb/[email protected] installed globally. Not platform-specific — the manifest is.
Workaround
npm install -g sharp-g matters: the CLI lives at <prefix>/lib/node_modules/@gitlawb/openclaude/dist/cli.mjs, and Node's resolution walks up to <prefix>/lib → <prefix>/lib/node_modules/sharp. No restart needed — getImageProcessor() caches only on success, so the next ctrl+v retries the import. Confirmed working.
Suggested fix
- Move
sharpback todependencies(oroptionalDependencies+peerDependenciesMetaif optional image support is the intent). - Add it to the clean-install check —
scripts/verify-clean-install.tsalready exists and would have caught this. - Stop swallowing
ImageProcessorUnavailableErroringetImageFromClipboard. It has a precise, actionable message ("Image support is not installed. Installsharp…"); surfacing it instead ofnullwould have made this self-diagnosing. The currentcatch { return null }converts a dependency error into a false statement about the user's clipboard. - Consider having
maybeResizeAndDownsampleImageBuffercheck dimensions and size before reaching for the processor, so images already under the limits pass through untouched.
Point 3 is the one that cost the most time here. The bug was findable in minutes; the wrong error message sent the search to the clipboard instead of the manifest.
Possibly related: #2198 (image paste broken after 0.21.0). The dependency move lands at 0.23.0, so it does not explain a 0.22.0 failure on its own, but any 0.23.0+ report of broken image paste is explained by this.
Source: Gitlawb/openclaude