GitHub has no attachment API, and a raw URL from a private repo never renders — camo can't authenticate. This drives GitHub's own uploader and hands back a URL that works anywhere.
You want a screenshot at the top of a PR. Every path that looks like it should work fails — and most of them fail silently, so you only find out when a reviewer sees a broken image icon.
gh can't do it.raw.githubusercontent.com from a private repo → does not render. GitHub proxies images through camo, and camo can't authenticate. Dead end.file_upload MCP tool → currently rejects host paths (regressed). Don't rely on it.cmd+v paste → unreliable; the key event never delivers image bytes to GitHub's paste handler.Stop trying to host the image. Make GitHub's real uploader run. Put the image on the clipboard as base64 text, then dispatch a genuine drop event carrying a reconstructed File onto a classic editor's <file-attachment> element. GitHub uploads it for real and inserts the markup — which we harvest. The bytes ride the OS clipboard; they never pass through the agent's context.
The classic editor only exists in a few places — not the React /issues/new page. The reliable one is any repo's wiki new-page, used purely as an uploader and never saved. The asset persists on its own.
Clipboard → drop → URL → body. Four moves:
# 1. image onto the clipboard as base64 text (macOS; note the -i) base64 -i /path/to/image.png | tr -d '\n' | pbcopy # 2. open a CLASSIC editor and prove it is one — both must be true # https://github.com/<owner>/<repo>/wiki/_new ({ hasFileAttachment: !!document.querySelector('file-attachment'), hasBody: !!document.querySelector('#gollum-editor-body') }) # 3. click the body for OS focus, then run scripts/drop-and-harvest.js # → { url, imgTag } # 4. spend the URL gh pr edit <N> --body-file body.md # <img width="1200" src="$URL" />
user-attachments asset is only visible to people who can see the repo it was uploaded through. Upload via the same repo as the PR — or one your audience can also see. Then navigate to the PR and actually look at it. A broken image means wrong repo scope or a bad URL; that eyeball check is the whole point.raw.githubusercontent.com is fine there.