Skip to content
Agentmart
Submit a tool

github-attach-media

Upload an image, screenshot, or video to get a renderable GitHub user-attachments URL for a PR, issue, comment, or Projects card, including private repos (no…

Skill by RascalTwo

IndexedMIT★ 2
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/rascaltwo-github-attach-media

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Upload an image, screenshot, or video to get a renderable GitHub user-attachments URL for a PR, issue, comment, or Projects card, including private repos (no attachment API; raw.githubusercontent won't render). Use when a PR or issue body needs a hosted image.

Use cases: backend

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/github-attach-media

What's inside

Skill name
github-attach-media
Description
Upload an image, screenshot, or video to get a renderable GitHub user-attachments URL for a PR, issue, comment, or Projects card, including private repos (no attachment API; raw.githubusercontent won't render). Use when a PR or issue body needs a hosted image.
License
MIT
Instructions
88 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts
  • Instructions call the network

Files (2)

  • SKILL.md
  • drop-and-harvest.js · scripts/

SKILL.md (first lines)

# GitHub upload: images and video (→ user-attachments CDN URL)

GitHub has **no attachment API**. The only way to get a renderable, access-controlled image URL
is GitHub's own uploader — which is a browser drag-drop onto a classic editor. This skill automates
that with Chrome MCP and hands you a `https://github.com/user-attachments/assets/<uuid>` URL you can
drop into any PR/issue/comment/Projects body via `gh`.

Why not the obvious alternatives:
- **`raw.githubusercontent.com` from a private repo** → does NOT render (camo can't authenticate). Dead end.
- **`file_upload` MCP tool** → currently rejects host paths (regressed). Don't rely on it.
- **Synthetic `cmd+v` paste of a clipboard image** → unreliable; the key event doesn't deliver image
  bytes to GitHub's paste handler. Use the **drop** path below instead.

## The mechanism

Put the image on the clipboard as base64 **text**, then dispatch a real `drop` event carrying a
reconstructed `File` onto a classic editor's `<file-attachment>` element. GitHub's real uploader runs
and inserts the `<img src="…user-attachments/assets/…">` markup, which we harvest. The bytes ride the
OS clipboard as text, never through the agent's context.

**Key constraint — you need a CLASSIC editor.** The `<file-attachment>` element only exists on GitHub's
classic Markdown editors, NOT the React `/issues/new` editor. The reliable one is any repo's **wiki new-page**:
`https://github.com/<owner>/<repo>/wiki/_new`. Use it purely as an uploader — **never save the page**;
the asset persists on its own once uploaded.

**Access control / which repo's wiki to use.** A `user-attachments` asset is scoped to whoever can see
the repo it was uploaded through. Prefer the **same repo** as the PR/issue so viewers can see it. If that
repo's wiki is disabled (its `/wiki/_new` redirects to the repo home), use another repo **your audience can
also see** (e.g. another repo in the same org). The asset stays private to those viewers.

## Steps

**1. Put the image on the clipboard as base64 text** (macOS):
```bash
base64 -i /path/to/image.png | tr -d '\n' | pbcopy
```
Note the `-i` — macOS `base64` needs it. (Linux: `base64 -w0 /path/to/image.png | xclip -selection clipboard`.)

**2. Open a classic wiki editor in Chrome** (load Chrome MCP tools first if deferred). Navigate to
`https://github.com/<owner>/<repo>/wiki/_new`, then verify it's really the classic editor:
```js
({ hasFileAttachment: !!document.querySelector('file-attachment'),
   hasBody: !!document.querySelector('#gollum-editor-body') })
```
Both must be `true`. If false, the wiki is disabled on that repo — try a different repo's wiki (see access-control note).

**3. Click into the editor body** (the "Page content" textbox) with the `computer` tool — this gives the
tab OS focus, required for `navigator.clipboard.readText()`.

**4. Drop + harvest.** Read `scripts/drop-and-harvest.js` and pass its contents to `javascript_tool`.
It rebuilds the File from the clipboard, dispatches the drop, polls for the URL, and returns
`{ url, imgTag }`. Grab `url`. Pass it **as-is** — it's top-level-await form on purpose; do NOT
re-wrap it in an `async () => {…}` IIFE (the tool returns the last expression but won't await a
Promise you hand back, so a wrapper serializes to `{}`). If you ever do get `{}`, the drop still
ran — just read the body to recover the URL:
`const v=document.querySelector('#gollum-editor-body').value; v.match(/user-attachments\/assets\/[0-9a-f-]+/)?.[0]`

**5. Use the URL.** Set it wherever you need it — the body is plain Markdown/HTML:
```bash
gh pr edit <N>   --body "<img width=\"1200\" src=\"$URL\" />\n\n$(cat body.md)"
gh issue edit <N> --body "..."
gh project item-edit --id <DI_…> --body "![viz]($URL)\n\n<existing>"
```
Prefer building the full body in a file and passing `--body-file` to avoid escaping pain.

**6. Discard the wiki editor — do NOT save.** Clear the body and drop the unsaved-changes guard, then
navigate away:
```js
const b = document.querySelector('#gollum-editor-body');
b.value = ''; b.dispatchEvent(new Event('input', {bubbles:true}));
window.onbeforeunload = null; true;
```
(If you navigate before clearing, Chrome throws a "Leave site?" dialog — clear first.)

## Verify
- **Bytes:** `bytes` in the result equals the file's size. For proof GitHub serves that exact file,
  fetch its signed URL (no browser needed) and hash it against the local file:
  `U=$(gh api markdown -f mode=gfm -f context=<owner/repo> -f text='![x](<url>)' | grep -o 'https://private-user-images[^"]*' | head -1 | sed 's/&amp;/\&/g'); curl -s "$U" | shasum -a256`
- **Page:** navigate to the PR/issue and look: the image renders (broken = wrong repo scope or a
  bad URL), a video shows a player. Chrome loads no video in a background tab, and a fresh upload
  can take a minute to serve, so a spinner there is not a failed upload; the hash above settles it.

## Notes
- `<img width="1200" height="630" …>` gives a stable render size for a 1200×630 OG card; `![alt](url)` is fine otherwise.
- The asset is permanent and independent of the wiki page — safe to leave the wiki unsaved.
- Several files: repeat steps 1, 3 and 4 per file; the script empties the editor each time.

From RascalTwo/ai-setup, MIT.

More from RascalTwo/ai-setup

Signals

Stars
2
Forks
0
Open issues
0
Last commit (this folder)
Oct 24, 2026 (4 days ago)
Last release
Oct 24, 2026 (4 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from RascalTwo

Cite this page

github-attach-media skill. Agent Mart. https://agentmart.dev/skills/rascaltwo-github-attach-media (last updated 2026-10-24)