Upload media files (screenshots, animated images, screen recordings — single or whole directory) to the public image-hosting repo (default ArcBlock/loop-agent-assets),
returnsanonymously accessible's raw.githubusercontent.com/.../main/... URL。
Output language: Chinese; code / paths / commands stay in the original text.
⚠️ Upload is unified, embedding is not (the easiest pitfall to hit)
Uploading is the same set for image/video; but how to embed in GitHub markdown is split by type:
| Type | Extension | Embedding method |
|---|---|---|
| Image | png / jpg |  Inline display(#1334) |
| Animated image | gif |  Inline playback(gif is image/gif, same path as images) |
| Video | webm / mp4 / mov | Link [▶ 录屏](<raw-url>), or best-effort <video src=...>;Never — GitHub will not render raw video as a player,![]() will only break the image |
The criterion only looks atextension:.png/.jpg/.gif → ![]();.webm/.mp4/.mov → link.
Mechanism = two generic scripts in the plugin (they run the same for any repo, automatically detecting the source repo from git remote , landing in <slug>/<ctx>/)
First resolve the plugin root; the scripts are all under its scripts/ :
PLUGIN="${AGENTLOOP_ROOT:-$HOME/.claude/plugins/marketplaces/arcblock-agent-skills/plugins/agentloop}"
- Single file:
bash "$PLUGIN/scripts/gh-upload-media.sh" <media> [name]→ automatically choose the write channel (gh contents API / git push) → content-type=image/*|video/*gate → stdout prints the raw.githubusercontent/main URL. - Whole directory:
SCREENSHOT_DIR=<dir> CONTEXT=<ctx> bash "$PLUGIN/scripts/gh-upload-dir.sh"→ traverse the directory (png/jpg/jpeg/gif/webm/mp4/mov) and call the above one for each → stdout one per linefilename\turl; if any fails, append a lineUPLOAD_FAILED\t<原因>(always exit 0, on failure look at this line, not the exit code).
Usage
/agentloop:media-upload <dir> # 上传目录下所有媒体 → filename\turl map
/agentloop:media-upload <file> # 单个(图片或视频)
# 消费方 skill 的 bash 里直接调脚本(自动化调机制、不调 slash 命令):
SCREENSHOT_DIR="$SHOT_DIR" CONTEXT="pr${PR}" bash "$PLUGIN/scripts/gh-upload-dir.sh" > url-map.tsv
After getting the map, follow the table above tosplit the embedding. When a UPLOAD_FAILED line is received, display at the top of the report > ⚠️ 媒体上传失败 — <原因>
and downgrade to SendUserFile direct delivery (cannot embed, but does not produce broken files).
When neither uploader channel is available: browser native attachment upload (third-layer fallback, issue #3010)
SendUserFile only sends the file to the person in the current conversation,will notlet the evidence land on GitHub — the PR/issue still has no verifiable
screenshot. This kind of review gating (ui-verify 's UI evidence gate is the most typical) requires evidencepersistently published on GitHub,gh and the image-hosting
repo clone are both unavailable, the only deterministic and repeatable fallback isdriving a browser already logged into GitHubto go through native attachment upload
(this round actually produced the screenshot evidence for arc#2991 / arcblock-site#165 / Site PR #166):
- Open the comment box of the target PR/issue, trigger the file chooser (drag and drop or click the attach button), and select the local media file.
- GitHub uploads the file to its own attachment CDN, returns
https://github.com/user-attachments/assets/<uuid>(or the olderuser-images.githubusercontent.com/...) URL and automatically inserts the reference into the comment body. - Before submitting, use a browser screenshot / read the DOM to confirm the media has rendered as a visible image/video, not a broken link — do not assume the upload succeeded.
- Post to both the PR and its associated issue(
Fixes #<n>/Part of #<n>), do not post in only one place.
This URL family (github.com/user-attachments/assets/…、user-images.githubusercontent.com/…) is, like
raw.githubusercontent.com/<repo>/main/… , gated downstream (e.g., .claude/skills/ui-verify/scripts/gate-comment.ts
's isPublishedUrl()) aspublishedevidence; local file paths,SendUserFile attachments, or any URL not verified as anonymously readable
do not count—upload/publish failures must never be described by downstream reports as "verification complete", if it should be labeled BLOCKED/"evidence missing" then
label it, without using neutral wording to obscure it (terminology consistent with ui-verify/pr-review/pr-sweep/issue-review ).
This fallback requiresan authenticated GitHub browser session, which is at a different level from gh-upload-media.sh 's two automated channels—it is not always
feasible in every environment; when infeasible, degrade per the previous section to SendUserFile, and consumer gating treats it as "evidence missing" and must not let it through.
Why it must be raw.githubusercontent.com/main (the script already enforces this, don't change it yourself)
- #1334: the GitHub MCP write side (
add_issue_comment/issue_write)'s sanitizer stripsdown to a plain link— the only exception is host=raw.githubusercontent.comand ref=main. Never usecdn.jsdelivr.net, and never use a session branch ref. (gh pr comment/gh issue comment's comments are not subject to this restriction, but uniformly using raw.githubusercontent/main is safe for both posting channels.) - #1079: the MCP file write tool (
create_or_update_file/push_files) double base64-encodes binarySecondary base64 encoding → it is stored as an ASCII string, not media.Media binaries must never go through any MCP write tool—the script goes through the gh contents API or git push, and does a content-type=image/*|video/*spot check on the returned URL (anything other than the two = double-encoded/corrupted, judged a failure).
Dual channels (the script selects automatically, callers need not care)
- Channel A —
ghcontents API:ghused when it exists and has write permission to the image-hosting repo (local PAT / token with write permission); with transient 5xx retries. - Channel B — git push: when there is no
gh(or no write permission),cpinto the already-cloned image-hosting repo workspace ($ASSETS_REPO_DIRor probe~/loop-agent-assetsetc.) →commit+push origin main(with fetch+rebase retries). In a cloud routine, the gh token is a placeholder (403 for write APIs to this org, #1334), and this is the path taken—provided the image-hosting repo is configured as a second git source.
If neither channel is available → the script exits 2 → consumers degrade to SendUserFile。
Consumers
test-sweep(arc)、ui-verify(arcblock-site, and in the future did) etc. call in their own bashgh-upload-dir.sh。- The mechanism is always those two scripts, don't hand-roll
for f in *.pngloops in each repo(arc and arcblock-site each wrote their own copy and they have drifted—#1037). The human-readable doctrine + discoverable entry point is this skill.