gh-assets-branch-mgmt
Manages GitHub assets branch for persistent image hosting in PRs. Creates orphan branch, uploads files, generates raw URLs. Bypasses transient CDN tokens. Triggers on keywords: assets branch, upload screenshots, pr images, persistent images, github assets
When & Why to Use This Skill
This Claude skill automates the management of a dedicated GitHub 'assets' branch to provide persistent hosting for images and videos used in Pull Requests. It solves the common issue of expiring CDN tokens in private repositories by generating permanent raw URLs, and features advanced media processing capabilities including automatic video-to-GIF conversion and optimized MP4 encoding for professional documentation.
Use Cases
- Persistent PR Documentation: Hosting screenshots and diagrams in private repository Pull Requests using permanent URLs that remain accessible long after the PR is created.
- Automated E2E Test Evidence: Seamlessly uploading video recordings and screenshots from Playwright or Cypress sessions to GitHub to provide visual proof for QA and code reviews.
- Optimized Media Embedding: Converting large screen recordings into high-speed, optimized GIFs and MP4s to create lightweight, auto-playing visual demos within GitHub descriptions.
- Repository Asset Lifecycle Management: Initializing orphan branches for clean storage and performing automated cleanup of temporary assets once a feature branch is merged.
| name | gh-assets-branch-mgmt |
|---|---|
| description | Manages GitHub assets branch for persistent image hosting in PRs. Creates orphan branch, uploads files, generates raw URLs. Bypasses transient CDN tokens. Triggers on keywords: assets branch, upload screenshots, pr images, persistent images, github assets |
| project-agnostic | true |
GitHub Assets Branch Management
Automates the "assets branch" strategy for persistent image hosting in GitHub PRs. Solves the problem of transient CDN tokens (?jwt=...) that expire on private repo images.
Playwright MCP Integration
CRITICAL: When using Playwright MCP for E2E testing, video is recorded automatically.
Video Recording (Automatic)
Playwright MCP configured with --save-video=<resolution> records ALL browser sessions:
- Storage:
.playwright-mcp/directory (project root) - Format:
.webmfiles with timestamp (e.g.,page-2026-01-06T22-49-59-407Z.webm) - Resolution: Matches config (default:
1920x1080)
Evidence Upload Workflow
When adding visual evidence to PRs:
| Evidence Type | Source | Tool |
|---|---|---|
| Screenshots | browser_take_screenshot |
Direct capture |
| Video | .playwright-mcp/*.webm |
Use existing file |
DO NOT record a new video when adding video evidence. The video already exists from the browser session.
Standard Workflow
- Screenshots: Use
browser_take_screenshotduring E2E validation - Video: After E2E session completes, find the
.webmin.playwright-mcp/ - Upload: Use this skill to upload screenshots + video (auto-converts to MP4+GIF)
# Upload all evidence (screenshots + video)
/gh-assets-branch-mgmt upload outputs/screenshots/*.png .playwright-mcp/*.webm --context pr-42
The video will be automatically converted to H.264 MP4 (8x speed) + GIF for PR embedding.
Commands
| Command | Description |
|---|---|
init |
Create orphan assets branch if not exists |
upload <files/glob> --context <id> |
Upload files to assets branch under context path |
list --context <id> |
List all assets for a context |
cleanup --context <id> |
Remove all assets for a context |
url <file> --context <id> |
Get raw URL for a specific file |
convert-video <input> [options] |
Convert video to optimized H.264 MP4 |
download-video <context> [options] |
Download videos from assets branch |
Usage
Initialize Assets Branch
/gh-assets-branch-mgmt init
Creates an orphan assets branch with no commit history using Git's well-known empty tree SHA.
Upload Files
/gh-assets-branch-mgmt upload outputs/screenshots/*.png --context pr-42
Uploads all matching files to assets branch under pr-42/ path.
Get URLs
/gh-assets-branch-mgmt url screenshot.png --context pr-42
Returns: https://raw.githubusercontent.com/OWNER/REPO/assets/pr-42/screenshot.png
Upload Videos
Videos are automatically converted and uploaded as both GIF + MP4:
/gh-assets-branch-mgmt upload ./recording.mov --context pr-42
The tool:
- Detects video by extension (mov, mp4, webm, avi, mkv, m4v)
- Converts to H.264 MP4 (720p, CRF 28, 8x speed by default)
- Generates GIF from the output MP4 (720px, 10fps, palette optimized)
- Uploads both GIF (for inline embed) and MP4 (for download)
- Outputs markdown for GIF embed + download link for MP4
- Cleans up temporary files
Output format:

[Download Recording](https://github.com/.../recording_converted.mp4?raw=true)
Why both? GIF embeds inline, MP4 provides full quality download option.
Note: Videos are sped up 8x by default (144s input -> 18s output). Audio is dropped.
Convert Video (Standalone)
/gh-assets-branch-mgmt convert-video ./demo.mov --preset pr-medium
Video Options:
--preset <name>- Compression preset (see table below)--max-size <mb>- Target file size in MB (enables two-pass encoding)--output <path>- Custom output path--speed <factor>- Playback speed multiplier (default: 8 = 8x faster)
GIF Options:
--gif- Also generate GIF from output MP4--gif-only- Generate only GIF, remove MP4 after--gif-fps <n>- GIF frame rate (default: 10)--gif-width <px>- GIF max width (default: 720)
Examples:
# Default: 8x speed, pr-small preset (MP4 only)
./gh-video-convert.sh demo.mov
# Generate MP4 + GIF
./gh-video-convert.sh demo.mov --gif
# Generate only GIF (no MP4 kept)
./gh-video-convert.sh demo.mov --gif-only
# Custom GIF settings
./gh-video-convert.sh demo.mov --gif --gif-width 1080 --gif-fps 24
# Original speed (no speedup)
./gh-video-convert.sh demo.mov --speed 1
Processing Pipeline:
Input Video -> MP4 (scaled, sped up) -> GIF (from MP4)
The GIF is always generated from the output MP4, ensuring visual consistency.
Video Presets
| Preset | Target Size | Resolution | CRF | Use Case |
|---|---|---|---|---|
pr-small |
~8MB | 720p | 28 | GitHub free plan, short demos |
pr-medium |
~25MB | 720p | 24 | Longer demos |
pr-large |
~50MB | 1080p | 23 | High quality demos |
demo |
~80MB | 1080p | 20 | Maximum quality |
Download Videos
Download uploaded videos from the assets branch:
/gh-assets-branch-mgmt download-video pr-42
Downloads the latest video and opens it automatically.
Options:
--all- Download all videos (default: latest only)--list- List videos without downloading--no-open- Don't open video after download--output <dir>- Download directory (default: /tmp)--repo <owner/repo>- Repository (default: current repo)
Examples:
# Download & open latest video
./gh-assets-download.sh pr-42
# Download all videos
./gh-assets-download.sh pr-42 --all
# List available videos
./gh-assets-download.sh pr-42 --list
# Download to specific directory
./gh-assets-download.sh pr-42 --output ./downloads
Cleanup After PR Merge
/gh-assets-branch-mgmt cleanup --context pr-42
Removes all files under pr-42/ from the assets branch.
Implementation Details
Orphan Branch Creation
Uses Git's well-known empty tree SHA to create a branch with no history:
EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
COMMIT=$(gh api repos/$OWNER/$REPO/git/commits -X POST \
-f message="chore: initialize assets branch" \
-f tree="$EMPTY_TREE" --jq '.sha')
gh api repos/$OWNER/$REPO/git/refs -X POST \
-f ref="refs/heads/assets" -f sha="$COMMIT"
File Upload Strategy
CRITICAL: Files must be uploaded sequentially (not in parallel). Use the bash script:
./gh-assets-upload.sh <source-dir> <context> <owner/repo>
Each file is uploaded via GitHub Contents API with inline base64 encoding:
gh api repos/OWNER/REPO/contents/{context}/{filename} -X PUT \
-f message="assets({context}): add {filename}" \
-f content="$(base64 -i {filepath})" \
-f branch="assets" \
--jq '.content.sha'
URL Format
Use blob URL with ?raw=true for embedding (works for both public and private repos):
https://github.com/{owner}/{repo}/blob/assets/{context}/{filename}?raw=true
Markdown syntax:

Directory Structure on Assets Branch
assets/
├── pr-1/
│ ├── screenshot1.png
│ └── screenshot2.png
├── pr-42/
│ └── demo.gif
└── README.md (optional)
Bash Script Tools
Scripts are located in this skill directory:
gh-assets-upload.sh- Batch upload with auto video conversiongh-video-convert.sh- Standalone video conversion toolgh-assets-download.sh- Download videos from assets branch
Usage (from skill directory):
./gh-assets-upload.sh ./screenshots pr-42
./gh-video-convert.sh demo.mov --gif
./gh-assets-download.sh pr-42
Error Handling
- Branch exists: Skip creation, proceed with upload
- File exists: Update (PUT) overwrites existing file
- Upload fails: Log error, continue with remaining files
- Invalid context: Must match pattern
[a-z0-9-]+ - Video conversion fails: Falls back to uploading original file
- ffmpeg not found: Logs error, skips video conversion
Limitations
Private Repo Image Embedding
For private repos, use the blob URL with ?raw=true format:

URL Format Comparison:
| Format | Private Repo | Public Repo |
|---|---|---|
raw.githubusercontent.com/.../assets/... |
404 | Works |
github.com/.../blob/assets/...?raw=true |
Works | Works |
Correct Pattern:
https://github.com/{owner}/{repo}/blob/assets/{context}/{filename}?raw=true
Video Size Limits
GitHub has different video size limits by plan:
| Plan | Max Video Size |
|---|---|
| Free | 10MB |
| Paid | 100MB |
Use --preset pr-small for free plan compatibility, or --max-size <mb> for precise control.
GIF Size Guidance
| Duration | Approx GIF Size (720px, 10fps) |
|---|---|
| 10s | ~5-8MB |
| 30s | ~15-25MB |
| 60s | ~25-40MB |
For large GIFs, try --gif-width 480 or --gif-fps 8.
Other Limitations
- File size: GitHub API limits (~100MB per file)
- Rate limits: Sequential uploads to avoid GitHub API rate limiting
- Public repos: Raw URLs work for embedding without issues
- Video formats: MP4 output is H.264, GIF uses palette optimization
- Audio: Dropped when speed > 1 (use
--speed 1to preserve audio) - GIF: Generated from output MP4, not raw input (ensures consistency)