mcpField
G
github-upload-image-to-pr

github-upload-image-to-pr

github.com/tonkotsuboy开发40156 次安装8 周前更新
代码仓库

关于 github-upload-image-to-pr

github-upload-image-to-pr 是将本地图片或视频添加到 GitHub 拉取请求的技能。优先使用可用的 `gh --attach`,并为旧版本或不支持的环境说明浏览器上传备用流程。

主要功能

  1. 01选择直接附加或浏览器上传
  2. 02通过 GitHub CLI 附加媒体
  3. 03使用浏览器作为上传备用方式
  4. 04将媒体加入 PR 描述或评论
  5. 05验证上传后的图片引用

适用场景

  1. 01为新建的拉取请求添加截图
  2. 02在 PR 评论中发布测试结果图片
  3. 03在 `gh --attach` 不可用时上传媒体

MCP 相关

备用流程可使用 Chrome DevTools MCP 或 Playwright MCP 控制现有浏览器来上传文件。优先选择 Chrome DevTools MCP,不可用时再用 Playwright MCP。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tonkotsuboy/github-upload-image-to-pr --skill github-upload-image-to-pr

安装后在 Claude Code 中输入/github-upload-image-to-pr确认已加载。

详细介绍

将图片上传到 PR

将本地图片添加到 PR 有两种方式,它们在成本和可靠性方面差异很大:

  • 路径 A — gh --attach(GitHub CLI 2.99.0+,于 2026-09 发布):一条 Shell 命令。无需浏览器、MCP 服务、快照或轮询。只要可用,就优先使用此方式。
  • 路径 B — 浏览器上传(备用方案):通过真实浏览器操作 GitHub 的评论框,这是在旧版 gh 上访问 GitHub 图片托管服务的唯一公开方式。步骤繁多、故障点多、耗费大量令牌。

之所以有路径 B,是因为 GitHub 的 REST API 仍然没有图片上传端点。路径 A 之所以可行,是因为现在 gh 会为你调用内部上传端点——因此应将路径 B 视为旧方案,而非同等的替代方案。

步骤 1:选择路径

GH_VER=$(gh --version 2>/dev/null | head -1 | awk '{print $3}')
echo "gh: ${GH_VER:-not installed}"
# Path A requires >= 2.99.0
[ -n "$GH_VER" ] && [ "$(printf '%s\n2.99.0\n' "$GH_VER" | sort -V | head -1)" = "2.99.0" ] \
  && echo "=> Path A (gh --attach)" || echo "=> Path B (browser fallback)"

符合以下任一条件时,请使用路径 B:

条件路径 A 无法使用的原因
gh 版本低于 2.99.0,或未安装尚无 --attach 标志
主机是 GitHub Enterprise Server附件功能仅支持 GitHub.com 和 Enterprise Cloud
没有仓库的推送权限(例如,这是其他人的分叉)--attach 上传需要推送权限
gh --attach 因不明原因失败不要在这上面耗费时间——记录错误并改用备用方案

两种路径都需要 gh(路径 B 仍通过 gh pr edit 嵌入图片),因此如果完全没有安装 gh,请先让用户安装:macOS 上运行 brew install gh,或参阅 https://cli.github.com/。

如果因为 gh 版本过旧而选择路径 B,请明确告知

用户即将花时间进行缓慢而不稳定的浏览器操作,而一条命令就能完成这项工作。值得先打断说明——但不值得因此停下,所以先告知用户,然后继续使用路径 B 并交付图片。回复开头可以这样写(使用与用户相同的语言):

⚠️ 更新 gh 后,这项工作只需一条命令 当前版本:gh 2.86.0 / 所需版本:2.99.0 或更高 更新后只需运行 gh pr comment 123 --attach ./shot.png 即可完成(无需操作浏览器,速度快且稳定)。 更新命令:brew upgrade gh 本次将继续使用浏览器自动化。

如果用户要求你升级,请执行升级、重新检查版本,并在当前请求中也改用路径 A。


路径 A — gh --attach(首选)

--attach 接受 <file> 或 <file>#<alt text>,可重复使用(每条命令最多 50 个文件,且不能重复附加同一个文件),并支持 gh pr create / pr edit / pr comment 以及对应的三个 gh issue 命令。

追加到 PR 描述
gh pr edit {PR_NUMBER} --attach './screenshot.png#Login error state'

省略 --body 可保留现有描述并追加附件——无需读取正文再拼接回去。

创建 PR 时附加

如果 PR 尚不存在,不要先创建再编辑——在同一次调用中附加:

gh pr create --title '...' --body '...' --attach './screenshot.png#Login error state'
控制图片的显示位置

如果正文中引用了本地路径,gh 会原位重写该引用,而不是追加内容,因此你可以按需排列图片(此时替代文本取自 Markdown,而非标志):

gh pr edit {PR_NUMBER} --body "$(gh pr view {PR_NUMBER} --json body -q .body)

## Screenshots

| Before | After |
| --- | --- |
| ![before](./before.png) | ![after](./after.png) |" \
  --attach ./before.png --attach ./after.png

这里有两点需要注意:--body 会替换描述,因此需要先读回现有正文;而且正文中的路径字符串必须与传给 --attach 的路径一致,重写才会生效。

改为发布评论
gh pr comment {PR_NUMBER} --attach './result.png#Test run summary'

使用 --body 时同样适用重写规则,因此评论也可以按需排版。

视频

mp4 / mov 的附加方式相同,并会显示为播放器。视频没有替代文本,因此不要添加 #... 后缀。

验证

无需浏览器——读回正文并统计附件数量:

gh pr view {PR_NUMBER} --json body -q .body | grep -c 'user-attachments/assets'
路径 A 的注意事项
  • 附加操作只会追加,绝不会替换。 重新运行同一条命令会重复添加图片。若要替换图片,请在附加新图片的同一条命令中,不带旧 URL 重写正文(gh pr edit {PR} --body "$(...)")。
  • 文件名中的 # 会破坏 <file>#<alt> 的拆分。 先用纯 ASCII 名称暂存一份副本(cp './weird #name.png' ./shot.png),之后再用 rm 删除。CleanShot X 在文件名中加入的 Unicode 窄空格也可以用同样的方法处理。
  • 部分失败时,成功的部分仍会生效。 如果有些文件上传成功,有些失败,PR 会更新为包含成功上传的文件,仍会打印 PR URL,但退出状态为非零。请查看输出并报告上传失败的文件,不要假设结果非成即败。
  • 仅接受图片和媒体文件,不接受 .zip 或 .log 等任意附件。

路径 B — 浏览器上传(适用于 gh < 2.99.0 的备用方案)

以下都是旧版流程。只有 Step 1 将你引导至此处时才使用。

由于旧版 gh 无法访问 GitHub 的图片主机,此路径将PR 评论文本框用作该主机的暂存区:先在其中上传文件以获取持久的 user-attachments/assets/ URL,然后通过 gh CLI 更新 PR 描述或发布评论。

Step B0:解析 PR 上下文并暂存文件

如果用户未指定 PR 编号或 URL,请自动检测:

# Get PR number from the current branch
gh pr view --json number,url -q '"\(.number) \(.url)"'

如果涉及多个仓库或分支,请与用户确认要操作哪个 PR。

另外,请将图片路径规范化为绝对路径,并在当前工作目录(仓库根目录)中暂存一份干净的副本,不要放在 /tmp/ 中:

# Stage inside the repo. The staged filename becomes the image's alt text on GitHub,
# so give it a meaningful name. Delete it after the upload completes.
cp /path/to/'CleanShot 2026-... .png' ./.upload-staging.png

为什么要在仓库中暂存,而不是放在 /tmp/?MCP 浏览器工具(Playwright / Chrome DevTools)只能读取其配置的工作区根目录内的文件;该目录通常是会话启动时所在的项目目录。/tmp/ 下的路径位于该根目录之外,因此上传调用会失败,并提示 Access denied: path ... is not within any of the configured workspace roots。在仓库中暂存文件适用于所有后端(agent-browser 和普通 CLI 也能读取 /tmp/,但暂存在仓库中始终安全)。

暂存还能避开带有特殊字符的路径(例如 CleanShot X 在文件名中加入的 Unicode 窄空格),否则这些字符会导致 shell 通配符匹配和工具参数出错。完成后记得用 rm 删除暂存文件,以免意外提交。

工具检测与选择
优先级顺序
  1. Chrome DevTools MCP(MCP 连接,mcp__chrome-devtools__*)——首选:连接到现有浏览器,保留登录状态,三个后端中最稳定
  2. Playwright MCP(MCP 连接,mcp__playwright__*)——仅在 Chrome DevTools MCP 不可用时使用;连接到现有浏览器,保留登录状态
  3. agent-browser(通过 Bash 使用 CLI——最后的备用方案,使用 --profile 保留登录状态)

基于 MCP 的工具会连接到已运行的浏览器实例,因此会自动保留 GitHub 登录状态。agent-browser 可通过 --profile ~/.agent-browser-github 持久保存登录状态。

检测
# 1. Search specifically for Chrome DevTools MCP first (preferred, most stable)
ToolSearch: "select:mcp__chrome-devtools__navigate_page,mcp__chrome-devtools__take_snapshot,mcp__chrome-devtools__upload_file,mcp__chrome-devtools__evaluate_script,mcp__chrome-devtools__click"

# 2. Only if Chrome DevTools MCP tools aren't found, search for Playwright MCP
ToolSearch: "browser navigate upload"

# 3. Fall back to agent-browser only if no MCP tools found at all
Bash: agent-browser --version
工具兼容性矩阵
操作Chrome DevTools MCP(首选)Playwright MCP(备用)agent-browser(CLI/Bash)
导航navigate_pagebrowser_navigateagent-browser --headed open {url}
快照take_snapshotbrowser_snapshotagent-browser snapshot
截图take_screenshotbrowser_take_screenshotagent-browser screenshot {path}
点击click (uid)browser_click (ref)agent-browser click {ref}
上传文件upload_file (uid, filePath)browser_file_upload (paths)agent-browser upload {ref} {path}
JS 求值evaluate_script (function)browser_evaluate (function)agent-browser eval '{js}'
登录状态保留保留使用 --profile 时保留
步骤 B1:导航到 PR 页面并检查登录状态

导航到 PR 页面后,立即截取快照以确认登录状态。

// Chrome DevTools MCP (preferred)
navigate_page({ url: "https://github.com/{owner}/{repo}/pull/{number}", type: "url" })

// Playwright MCP (fallback if Chrome DevTools MCP is unavailable)
browser_navigate({ url: "https://github.com/{owner}/{repo}/pull/{number}" })

// agent-browser (last-resort fallback; use --profile to persist login state)
agent-browser --headed --profile ~/.agent-browser-github open "https://github.com/{owner}/{repo}/pull/{number}"

如果出现 SSO 身份验证界面: 截取快照,找到“Continue”按钮并点击。

如果尚未登录(仅限 agent-browser):

  1. 导航到 https://github.com/login
  2. 请用户在有头浏览器窗口中手动登录。
  3. 等待用户确认,然后返回 PR 页面。
步骤 B2:找到上传目标

滚动到 PR 底部的评论表单并截取快照。

将快照保存到文件,而不是直接返回。 PR 页面快照很容易就有几百行(描述 + 每条审查评论),而你只需要其中一个 uid。Chrome DevTools MCP 的 take_snapshot 接受 filePath,因此可以将快照写入文件,再用 grep 查找放置区:

// Chrome DevTools MCP — write the snapshot out, then grep for the uid
take_snapshot({ filePath: "./.upload-snapshot.txt" })
grep -n "Paste, drop, or click to add files" ./.upload-snapshot.txt
#   549:      uid=3_529 button "Paste, drop, or click to add files"

完成后删除快照文件。如果不这样做,快照的成本可能看起来高得令人望而却步,导致你放弃使用 upload_file,转而采用糟糕得多的变通方法(将图片以内联 base64 的形式放进 evaluate_script 正文中——数千个必须逐字节准确转录的字符)。

关键注意事项: GitHub 实际的 <input type="file">(id fc-new_comment_field)设置了 display:none,因此不会出现在无障碍快照中——你无法通过这种方式获取它的 uid/ref,也无法点击它。应改为定位打开文件选择器的可见控件:标有 “Paste, drop, or click to add files” 的放置区,或工具栏中的 “Attach files” 按钮。浏览器工具会点击该控件、拦截原生文件选择器,并接收你的文件。

在快照中,它们会显示为:

button "Attach files"
button "Paste, drop, or click to add files"   ← pass this uid/ref to the upload tool

评论框本身是一个 <file-attachment> Web 组件,其中包裹着 <textarea id="new_comment_field">——添加的图片引用会出现在这个文本区域中(步骤 B4)。GitHub 的界面会随时间变化;如果找不到 new_comment_field,则改用 textarea[name="comment[body]"] 或 textarea[id*="comment"];PR 描述编辑器使用的则是 textarea[name="pull_request[body]"] / textarea[id$="-body"]。

步骤 B3:上传图片

使用检测到的工具上传每个文件,并传入步骤 B2 中获取的放置区 / 附件按钮 uid(切勿使用隐藏的输入框):

// Chrome DevTools MCP (preferred): upload_file({ uid: <dropzone uid>, filePath: <path inside the workspace root> })
// Playwright MCP (fallback):       browser_file_upload({ paths: [<path inside the workspace root>] }) once the file chooser is open
// agent-browser (last resort):     agent-browser upload {ref} {absolute_path}    (any path, /tmp/ ok)

对于 MCP 后端,请使用仓库内暂存的路径(参见步骤 B0 中关于工作区根目录的说明)。每次上传之间等待 2–3 秒。如果有多张图片,请先将它们全部上传到同一个评论框,再提取 URL——这样比在多次上传之间来回导航更高效。

步骤 B4:获取已上传图片的 URL

上传期间,GitHub 会在文本框中显示占位符(![Uploading file.png…]()),文件上传完成后再将其替换为最终引用。因此,请轮询文本框,直到出现 user-attachments/assets/ URL,不要依赖固定的等待时间——通常需要 1–5 秒。

GitHub 会将引用插入为 HTML <img> 标签(包含自动检测的 width/height),例如:

<img width="342" height="354" alt="filename" src="https://github.com/user-attachments/assets/8fc1b84a-..." />

不要假设它会采用 ![alt](url) Markdown 格式——直接匹配资源 URL 本身,确保提取过程无论引用采用哪种形式都足够可靠:

// MCP-based tools — returns every asset URL plus the raw value for sanity-checking
() => {
  const ta = document.getElementById('new_comment_field')
          || document.querySelector('textarea[name="comment[body]"], textarea[id*="comment"]');
  if (!ta) return { error: 'textarea not found' };
  const urls = [...ta.value.matchAll(/https:\/\/github\.com\/user-attachments\/assets\/[0-9a-fA-F-]+/g)].map(m => m[0]);
  return { raw: ta.value, urls };
}
# agent-browser
agent-browser eval 'const ta=document.getElementById("new_comment_field")||document.querySelector("textarea[id*=comment]");JSON.stringify([...(ta?.value||"").matchAll(/https:\/\/github\.com\/user-attachments\/assets\/[0-9a-fA-F-]+/g)].map(m=>m[0]))'

如果要保留 GitHub 自动检测的 width/height,请保留整个 <img …> 标签;否则只提取 URL,再自行套用格式(![alt](url) 或自定义的 <img>)。

步骤 B5:清空文本框(不要提交评论)

清空后触发 input 事件,以便 GitHub 的评论草稿自动保存功能也同步清空;否则,暂存的内容可能会在下次加载页面时再次出现。

// MCP-based tools
() => {
  const ta = document.getElementById('new_comment_field')
           || document.querySelector('textarea[name="comment[body]"], textarea[id*="comment"]');
  if (!ta) return "textarea not found";
  ta.value = "";
  ta.dispatchEvent(new Event('input', { bubbles: true }));
  return "cleared";
}
# agent-browser
agent-browser eval 'const ta=document.getElementById("new_comment_field")||document.querySelector("textarea[id*=comment]"); if(ta){ta.value="";ta.dispatchEvent(new Event("input",{bubbles:true}))} "cleared"'
步骤 B6:在 PR 中嵌入图片

可使用 GitHub 提供的完整 <img …> 标签(保留自动检测的尺寸),也可使用纯 Markdown ![alt](url) 格式嵌入图片。

选项 A — 更新 PR 描述(将图片追加到现有正文):

EXISTING_BODY=$(gh pr view {PR_NUMBER} --json body -q .body)

gh pr edit {PR_NUMBER} --body "$(printf '%s\n\n## Screenshots\n\n%s' "$EXISTING_BODY" '<img width="800" alt="screenshot" src="https://github.com/user-attachments/assets/..." />')"

选项 B — 发布为新评论:

gh pr comment {PR_NUMBER} --body '## Screenshots

<img width="800" alt="screenshot" src="https://github.com/user-attachments/assets/..." />'

默认使用选项 A,除非用户明确要求发布评论,或 PR 描述已经很长、改用评论会更清晰。

步骤 B7:验证结果

重新加载页面并截取屏幕截图,确认图片显示正常。

另外,还要通过编程方式确认图片确实已加载。两种 URL 格式都要匹配:渲染时,GitHub 会将 user-attachments/assets/... 重写为签名后的 private-user-images.githubusercontent.com URL,因此只搜索嵌入时使用的格式会得到 0 张图片,让人误以为没有附加任何图片。

// MCP-based tools — every embedded image plus whether the browser decoded it
() => {
  const imgs = [...document.querySelectorAll('img')]
    .filter((el) => /user-attachments|private-user-images/.test(el.src));
  return imgs.map((el) => ({ alt: el.alt, loaded: el.naturalWidth > 0 }));
}

如果每个条目都显示 loaded: true,表示嵌入成功。若返回空数组,表示 <img> 标签没有写入正文——请重新检查步骤 B6。

提示

  • 每次都先尝试路径 A:一次 gh pr edit --attach / gh pr comment --attach 调用即可取代下文的整个浏览器会话。只有步骤 1 中列出的条件才足以使用路径 B
  • 图片尺寸:--attach 会让 GitHub 自动选择尺寸;在路径 B 中(或手动编写标记时),可通过 <img width="800" alt="description" src="..." /> 控制尺寸
  • 多张图片:路径 A 会在一条命令中重复使用 --attach;路径 B 会将所有图片上传到同一个文本框,然后在清空文本框前提取所有 URL
  • 在路径 B 中优先使用 Chrome DevTools MCP:这是最稳定的后端——务必先通过 ToolSearch 尝试使用它。只有找不到 Chrome DevTools MCP 工具时才回退到 Playwright MCP,只有完全没有可用 MCP 工具时才使用 agent-browser
  • agent-browser 登录持久化:使用 --profile ~/.agent-browser-github,以便在多个会话间保留 GitHub 登录状态

故障排查

路径 A(gh --attach)
问题解决方案
unknown flag: --attachgh 版本低于 2.99.0——提示用户升级(步骤 1),然后继续使用路径 B
重新运行后图片重复--attach 只会追加。在同一次 gh pr edit --body ... --attach ... 调用中重写正文,并移除过时的 URL
上传遭拒 / 权限错误--attach 需要推送权限,且仅适用于 GitHub.com 和 Enterprise Cloud。请回退到路径 B
替代文本吞掉了文件名,或找不到文件路径中的 # 会被解析为替代文本分隔符——请用纯 ASCII 名称暂存副本
部分文件已上传,但命令以非零状态退出这是部分失败时的预期行为:成功上传的文件仍会保留在 PR 中。请报告哪些文件失败,并仅重试这些文件
正文引用未被替换,图片反而追加到了末尾正文中的路径必须与 --attach 路径字符串完全一致
路径 B(浏览器上传)
问题解决方案
未登录(MCP 工具)可能会出现 SSO 页面 — 截取快照,找到“Continue”按钮并点击
未登录(agent-browser)使用 --headed 模式,导航到登录页面,并请用户手动登录
浏览器窗口不可见对于 agent-browser,请确保使用 --headed 标志
文件路径包含特殊字符(例如,CleanShot 使用的 Unicode 窄空格)在仓库内以简单名称暂存副本:cp /path/'CleanShot ... .png' ./.upload-staging.png(放在仓库内,以便 MCP 工具读取 — 参见步骤 B0)
Access denied: path ... not within any of the configured workspace rootsMCP 浏览器工具只能读取其工作区根目录内的文件。请将图像暂存到仓库内(步骤 B0),不要放在 /tmp/
找不到文件输入框的 uid/ref<input type="file"> 的 display:none,不会出现在快照中 — 请改为定位可见的 “Paste, drop, or click to add files” 拖放区域或 “Attach files” 按钮(步骤 B2)
PR 页面快照过大 / 截取成本太高将 filePath 传给 take_snapshot,然后对该文件使用 grep 查找拖放区域的 uid(步骤 B2)。不要因为快照过大,就在 evaluate_script 中内联 base64
验证结果为 0 张图像,尽管图像已显示GitHub 渲染时会将资源 URL 改写为 private-user-images.githubusercontent.com — 同时匹配该域名和 user-attachments,然后检查 naturalWidth > 0(步骤 B7)
文本框中有文件,但没有 ![](url) MarkdownGitHub 会为图像插入 <img …> HTML 标签,而不是 Markdown。使用步骤 B4 中同时匹配这两种形式的正则表达式提取 user-attachments/assets/ URL
文本框中尚无 URLGitHub 会先显示 ![Uploading…]() 占位符 — 轮询文本框,直到出现 user-attachments/assets/ URL(1–5 秒),不要固定等待
找不到文本框选择器GitHub UI 偶尔会变更 — 依次尝试 new_comment_field → textarea[name="comment[body]"] → textarea[id*="comment"](步骤 B2)
Chrome DevTools MCP 已断开连接通过 /mcp 命令重新连接
找不到 agent-browsernpm install -g agent-browser && agent-browser install
找不到浏览器工具使用 ToolSearch 搜索可用的浏览器工具
找不到 PR / 404私有仓库会向未通过身份验证的用户返回 404 — 检查登录状态

注意事项

  • gh --attach 于 GitHub CLI 2.99.0(2026-09)中加入,支持 gh pr create / pr edit / pr comment,以及对应的 gh issue create / issue edit / issue comment 命令。每条命令最多可附加 50 个文件;同一文件不能重复附加
  • 使用 gh pr edit --attach 且不带 --body 时,会保留现有描述并追加内容 — 只有在需要控制内容位置时,才需要执行读取-修改-写入操作
  • GitHub 的 user-attachments/assets/ URL 是永久有效的 — 即使不提交评论,图像仍可访问。渲染时,GitHub 会将其改写为 private-user-images.githubusercontent.com CDN URL;user-attachments/assets/... 格式才是稳定的嵌入形式
  • 使用路径 B 上传时,GitHub 会注入 <img width=… height=… alt=… src=… /> 标签(alt 根据文件名生成)— 而不是 ![](…) Markdown。请通过资源 URL 提取,而不是通过外层标签提取
  • MCP 浏览器工具只能读取其工作区根目录内的上传文件,因此请将图像暂存到仓库内,而不是 /tmp/
  • 由于 GitHub UI 结构会发生变化,直接在浏览器 UI 中编辑描述并不可靠 — 强烈建议通过 gh pr edit 更新
  • 基于 MCP 的工具会连接到现有浏览器实例,从而保留 Cookie 和登录会话

评论

支持 Markdown