Upload images to GitHub Issue/PR comments and insert them with fixed width.
Works with both GitHub.com and GitHub Enterprise.
- gh CLI (authenticated)
- playwright-cli (browser/direct mode, not needed for
--releasemode) - jq (direct mode only)
brew tap atani/tap # first time only
brew install gh-attach
# First run: login to GitHub in browser
gh-attach --issue 1 --image ./test.png --headedgh extension install atani/gh-attach
# Use as: gh attach
gh attach --issue 123 --image ./screenshot.png- Install dependencies:
# gh CLI (if not installed)
brew install gh
gh auth login
# playwright-cli
npm install -g @playwright/mcp- Add to PATH:
# Option A: Symlink
ln -s /path/to/gh-attach/bin/gh-attach /usr/local/bin/gh-attach
# Option B: Add to PATH
export PATH="/path/to/gh-attach/bin:$PATH"Login to GitHub in the browser session:
gh-attach --issue 1 --image ./test.png --headed
# Browser opens → Login to GitHub → Session is saved for future usegh-attach --issue 123 --image ./screenshot.pnggh-attach --issue 123 --image ./e2e.png --body "E2E test result:"gh-attach --issue 123 \
--image ./before.png \
--image ./after.png \
--body 'Before: <!-- gh-attach:IMAGE:1 -->
After: <!-- gh-attach:IMAGE:2 -->'gh-attach --issue 123 --image ./screenshot.png --releaseFor hosts configured in ~/.config/gh-attach/config, direct mode is auto-enabled. This uploads via the upload policies API + curl, producing user-attachments URLs without creating release artifacts.
# ~/.config/gh-attach/config
# direct_hosts=your-ghe-host.com
gh-attach --issue 123 --image ./screenshot.png --host your-ghe-host.com --repo owner/repoUse --browser to override and force browser mode:
gh-attach --issue 123 --image ./screenshot.png --browsergh-attach --issue 123 --image ./result.png --body-file report.mdControl where images are inserted in the comment body:
| Placeholder | Description |
|---|---|
<!-- gh-attach:IMAGE --> |
Single image (or first image) |
<!-- gh-attach:IMAGE:1 --> |
First image (numbered) |
<!-- gh-attach:IMAGE:2 --> |
Second image |
<!-- gh-attach:IMAGE:N --> |
N-th image |
If no placeholder is present, images are appended to the end.
| Option | Required | Default | Description |
|---|---|---|---|
--issue <number> |
Yes | - | Issue or PR number |
--image <path> |
Yes | - | Image file (can be repeated) |
--repo <owner/repo> |
No | current repo | Target repository |
--width <px> |
No | 800 | Image width in pixels |
--body <text> |
No | - | Comment body text |
--body-file <path> |
No | - | Read body from file |
--host <host> |
No | auto-detected | GitHub host (for Enterprise) |
--release |
No | - | Use GitHub Releases API (no browser needed) |
--release-tag <tag> |
No | gh-attach-assets | Release tag for uploads |
--browser |
No | - | Force browser mode (skip direct upload) |
--headed |
No | - | Show browser window |
- Create a comment with placeholder(s)
- Open GitHub in browser via playwright-cli
- Upload image(s) using GitHub's native upload UI
- Extract the uploaded URL(s)
- Update the comment with
<img>tags
- Create a comment with placeholder(s)
- Upload image(s) to a GitHub Release via
gh release upload - Update the comment with release download URLs
- Create a comment with placeholder(s)
- Open GitHub in browser via playwright-cli (for authentication)
- Trigger the file-attachment component to obtain upload policies
- Upload file(s) via curl to the media server
- Update the comment with
user-attachmentsURLs
Direct mode is auto-enabled for hosts listed in ~/.config/gh-attach/config:
direct_hosts=host1.example.com,host2.example.com
- PR comments use the same API as issue comments (use PR number)
- Images are inserted as HTML:
<img src="..." width="800" alt="..."> - Browser session is persisted, so login is only needed once
- Use
--headedto debug or when login is required