深入解析 MLflow 仓库 upload-media Skill:本地媒体一键上传 GitHub user-attachments 并嵌入 PR 评论
【免费下载链接】mlflowThe open source AI engineering platform for agents, LLMs, and ML models. MLflow enables teams of all sizes to debug, evaluate, monitor, and optimize production-quality AI applications while controlling costs and managing access to models and data.项目地址: https://gitcode.com/GitHub_Trending/ml/mlflow
导读
MLflow 开源仓库在.claude/skills/upload-media/目录下内置了一个名为upload-media的 Claude Code Skill,用于把本地截图、录屏等媒体文件一次性上传到 GitHub 的user-attachments附件存储,并为每个文件返回可直接嵌入 Markdown 的https://github.com/user-attachments/...链接。本文以该 Skill 的官方定义文档(SKILL.md)为主体,结合仓库内完整的命令实现(upload_media.py)、底层上传模块(uploads.py)与测试用例(test_upload_media.py、test_uploads.py),带你掌握:如何调用该命令、输出格式是什么、图片与视频分别如何正确嵌入 PR 正文/Issue/评论,以及底层端点、认证、大小限制与错误处理的具体行为。
一、Skill 的定位与使用场景
根据 SKILL.md 的 frontmatter 定义,该 Skill 的用途是:
Upload one or more local images or videos to GitHub and get back a
user-attachmentsURL for each, to embed in a PR body, issue, or comment. Use when asked to attach screenshots or screen recordings.
也就是说,当 Claude 在代码审查、问题回复等场景中被要求附上截图或录屏时,就通过该命令把本地媒体上传到 GitHub,拿到链接后嵌入 PR 正文、Issue 或评论。argument-hint指明其参数为"要上传的图片或视频的路径"。
整个 Skills 体系位于 .claude/skills/,是一个名为skills的 Python 包,通过 pyproject.toml 注册了skills = "skills.cli:main"控制台入口,并由 .claude/skills/README.md 给出统一的调用方式:
uv run --package skills skills <command> [args]upload-media只是其中一条子命令(其余还有embed-media、pr-review、analyze-ci等)。它依赖的命令实现位于 .claude/skills/src/skills/commands/upload_media.py。
二、命令用法与输出格式
1. 基本调用
SKILL.md 给出的核心命令只有一行:
uv run --package skills skills upload-media <path>...<path>...是一个或多个媒体文件路径,多个文件依次上传;- 当参数为空时,SKILL.md 约定使用请求中直接提到的路径("when empty, the paths named in the request")。
从 upload_media.py 的 argparse 定义看,除位置参数paths外,还支持一个可选参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
paths(位置参数) | 必填(nargs="+") | 要上传的媒体文件,一个或多个 |
--repo | mlflow/mlflow | 附件绑定的目标仓库,格式为owner/repo,如--repo harupy/mlflow |
2. 输出格式
命令对每个文件输出一行,以 Tab 分隔,格式固定为:
<path>\t<url>即本地路径 + Tab + 上传成功后返回的 user-attachments URL。test_upload_media.py 的test_prints_a_url_for_each_file用例验证了这一点:两个文件(shot.png与clip.mp4)各输出一行<path>\t<url>。
3. 使用示例
# 上传单张截图 uv run --package skills skills upload-media /tmp/experiment-ui.png # 输出:/tmp/experiment-ui.png https://github.com/user-attachments/assets/2f1c0a3e-0000-4000-8000-000000000001 # 同时上传图片和录屏 uv run --package skills skills upload-media screenshot.png demo.mp4 # 上传到指定仓库 uv run --package skills skills upload-media --repo mlflow/mlflow screenshot.png三、底层执行流程(源码级拆解)
命令run的执行路径(upload_media.py)分三步:取凭证 → 解析仓库 ID → 逐个上传并打印 URL。
1. GitHub 凭证解析
凭证解析在 .claude/skills/src/skills/github/utils.py 中实现,优先级为:
- 环境变量
GH_TOKEN(存在则直接使用); - 否则调用
gh auth token获取已登录 CLI 的令牌; - 两者都没有时打印错误并退出:
Error: GH_TOKEN not found (set env var or install gh CLI)2. 仓库 ID 解析
resolve_repository_id(upload_media.py)通过 GitHub CLI 查询数字仓库 ID:
gh api repos/{repo} --jq ".id"默认查询mlflow/mlflow,返回类似136202695的数字 ID。如果gh调用失败(如仓库不存在返回 404,或未安装ghCLI),错误信息中的可操作部分(stderr)会被原样打印到 stderr 后以退出码 1 结束——test_upload_media.py 专门验证了这一行为,避免用户只看到 "returned non-zero exit status 1" 这种无意义信息。
3. 逐个上传与失败处理
主循环对每个路径做三件事:
- 路径不是文件(
not path.is_file())时打印failed <path>: not a file到 stderr,置失败标记; - 上传成功则打印
path\turl到 stdout; - 上传抛
UploadFailed时,若异常fatal为真(说明该故障与当前文件无关、剩余文件也会同样失败),则打印提示并break中止整个批次;否则只记录失败并继续处理下一个文件。其中 401 场景还会追加提示; check GH_TOKEN or run 'gh auth login'(upload_media.py)。
任何文件失败都会导致最终sys.exit(1)(第 72-73 行)。测试 test_upload_media.py 验证了:401/403/404 这类凭证级故障会立即停止剩余上传,而单个文件自身的失败(如不支持的扩展名)不会阻塞同批次的其他文件。
四、上传端点与文件约束
1. 端点与请求构造
底层上传实现在 uploads.py 的upload_asset函数(第 119-185 行),目标是 GitHub 未公开文档化的接口:
https://uploads.github.com/user-attachments/assets请求为POST,Query 参数包含三项:name(文件名)、content_type(MIME 类型)、repository_id(数字仓库 ID);请求体为文件原始字节,Header 携带Authorization: Bearer <token>与Accept: application/json,超时 60 秒。测试 test_uploads.py 对请求 URL 的编码(如content_type=image%2Fpng)、鉴权头和请求体都做了断言。
2. 支持的文件类型(MIME 白名单)
MIME_TYPES(uploads.py)限制了可上传的扩展名,扩展名必须与 content_type 一致,否则端点返回 422:
| 扩展名 | MIME 类型 | 类别 |
|---|---|---|
.png | image/png | 图片 |
.jpg/.jpeg | image/jpeg | 图片 |
.gif | image/gif | 图片 |
.webp | image/webp | 图片 |
.mp4 | video/mp4 | 视频 |
.mov | video/quicktime | 视频 |
.webm | video/webm | 视频 |
源码注释明确了两点边界:音频格式会被端点拒绝(即使 GitHub 官方文档声称支持附件音频);.svg技术上能上传成功,但因为还没有人确认 GitHub 会在 Markdown 中渲染 svg 附件,所以被刻意排除在白名单外。扩展名不在表中时,直接抛UploadFailed: unsupported extension。
3. 大小限制
- 图片上限
MAX_IMAGE_BYTES = 10 * 1024 * 1024(10 MB); - 视频上限
MAX_VIDEO_BYTES = 100 * 1024 * 1024(100 MB); - 空文件也会被拒绝(
the file is empty)。
max_bytes按扩展名区分视频与图片(uploads.py)。test_uploads.py 中的test_a_video_between_the_image_and_video_caps_is_not_skipped特意验证:介于 10MB 与 100MB 之间的录屏不会因旧的单一 10MB 上限被误杀。
五、嵌入规则:图片与视频的区别
SKILL.md 给出了最关键的使用约定:
Embed an image as
alt. Embed a video as the bare URL in its own paragraph, blank line above and below; anything else renders as a link rather than a player.
- 图片:用标准 Markdown 图片语法嵌入,
alt,alt为替代文本; - 视频:必须把 URL 单独放在一个段落里(前后各空一行),GitHub 才会渲染成播放器;否则只会渲染成普通链接。
这一规则在 embed_media.py 中被实现并进一步细化:standalone_pattern(第 33-35 行)用正则^[ \t]*!?\[[^\]]*](url)[ \t]*$识别"单独成行的引用";对视频引用,若其已单独成行则提升为裸 URL,若夹在句中被![]()包裹则会渲染为损坏图片,因此会去掉!使其降级为链接(第 41-47 行)。
六、错误语义:HTTP 状态码与 fatal 判定
UploadFailed异常携带status(HTTP 码)与fatal属性(uploads.py)。FATAL_STATUSES = {401, 403, 404, 429}——这些故障与当前文件无关,重试剩余文件必然同样失败,因此会中止整个批次。各状态码的具体解读:
| 状态码 | 含义与处理 |
|---|---|
401 | 凭证被拒绝;提示检查GH_TOKEN或执行gh auth login |
403 | 凭证未授权到该仓库(未设置正确的权限/范围) |
404 | 二义性:要么repository_id无法解析,要么端点拒绝该凭证。此时会用describe_token按令牌前缀(gho_OAuth、ghp_经典 PAT、github_pat_细粒度 PAT、ghu_App user-to-server、ghs_App/Actions、ghr_refresh)指出凭证类型,但绝不打印凭证本身 |
429 | 限流;若响应头带Retry-After则报告等待秒数,否则从响应体解析限流原因(主限流与次级限流行为不同) |
其它(如500) | 非 fatal,可继续下一个文件;422会解析响应体中的errors字段,剥离面向网页上传器的 HTML 标记后输出具体原因(如 "Yowza that's a big file"),并区分"类型不合法"与"文件过大" |
测试 test_uploads.py 与 test_rate_limiting_stops_the_run_and_reports_the_wait 对这些消息格式均有精确断言。
七、配套命令 embed-media:从"上传"到"替换引用"
除手动上传外,仓库还提供embed-media子命令(embed_media.py),可看作 upload-media 的自动化升级版,用于 PR 审查流程中批量替换引用:
uv run --package skills skills embed-media --dir <媒体目录> --target <目标文件> --repository-id <数字仓库ID>核心行为:
--dir为存放截图的目录,--target是要改写的文件(.json格式的 pr-review 载荷,或任意 Markdown 文件);- 只上传**目标文本中真正被引用(
...形式)**的文件,未被引用的视为草稿跳过; - 上传成功后把 Markdown 中的本地路径正则替换为
user-attachmentsURL;引用不存在的文件则剥离 Markdown 标记、降级为纯文本,避免发布指向本地路径的死链; --check模式只做静态检查(引用是否可解析、扩展名/大小是否合法、视频是否夹在段落中间等),不执行任何上传;- 安全细节:
collect_files(第 59-73 行)会跳过符号链接文件——因为is_file()会跟随 symlink,恶意放置的secret.png -> /proc/self/environ会把本进程的GH_TOKEN作为附件发布出去;未获取到令牌时不阻塞改写流程,但会明确打印no GitHub token; not uploading。
八、限制与注意事项
SKILL.md 在最后给出了一条重要提醒,属于必须知晓的工程风险:
No GitHub documentation covers this endpoint, so it can stop working without notice.
user-attachments上传接口没有官方文档,GitHub 随时可能改变或下线该行为而无任何通知。因此:
- 该命令适合开发期/审查期的日常使用,不应作为关键生产链路的一部分;
- 遇到 404/422 等异常时,优先参照上文错误语义自查(仓库 ID、凭证类型、文件格式、大小限制);
.svg与音频文件目前明确不支持,详见 uploads.py 中的 MIME 白名单注释。
九、延伸阅读
- Skill 定义文件:.claude/skills/upload-media/SKILL.md
- 命令实现:.claude/skills/src/skills/commands/upload_media.py
- 底层上传模块:.claude/skills/src/skills/github/uploads.py
- 凭证解析:.claude/skills/src/skills/github/utils.py
- 引用改写命令:.claude/skills/src/skills/commands/embed_media.py
- 测试用例:.claude/skills/tests/test_upload_media.py、.claude/skills/tests/test_uploads.py
- Skills 包说明与入口:.claude/skills/README.md、.claude/skills/pyproject.toml
【免费下载链接】mlflowThe open source AI engineering platform for agents, LLMs, and ML models. MLflow enables teams of all sizes to debug, evaluate, monitor, and optimize production-quality AI applications while controlling costs and managing access to models and data.项目地址: https://gitcode.com/GitHub_Trending/ml/mlflow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考