
qwen-code PR 证据托管迁移指南从 Git 分支到阿里云 OSS 的不可变对象前缀设计【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文基于 qwen-code 仓库的设计文档 docs/design/2026-08-25-pr-evidence-oss-hosting.md讲解自动化 PR 校验链路如何将截图证据的托管方式从写入仓库的pr-assets/*图片分支迁移为上传至阿里云 OSS 的不可变对象前缀。读完本文你将掌握两个可信发布者任务Web Shell 视觉预览发布者与/verify沙箱报告发布者的完整对象键设计、共享上传器脚本 scripts/upload-aliyun-oss-assets.js 的重试与超时语义、bucket 选择回退规则ALIYUN_OSS_PR_ASSETS_BUCKET变量以及遗留 Git 分支的清理与兼容策略。背景为什么不能再把截图提交进仓库分支问题根源克隆流量与仓库存储的隐性膨胀此前自动化的 PR 校验流程把截图直接提交到主仓库的pr-assets/*分支上。一个普通git clone会拉取所有被通告分支可达的对象因此这些长期存活、且只包含 PNG 图片的分支会给每次克隆增加数百 MB 的传输流量与仓库存储开销——而这些图片与产品源码树毫无关系。对每个 PR 都要克隆仓库的 CI 场景而言这一成本会随 PR 数量线性放大。设计目标迁移方案确立了如下目标原文 Goals 的完整转述保留 Web Shell 视觉预览与/verify报告中的内联截图能力停止自动化工作流在该仓库中创建或更新图片分支复用现有、已经过测试的阿里云 OSS 上传器与仓库凭据保留当前的校验逻辑、大小限制、重试与 fail-safe降级行为通过可信发布者任务将 PR 派生字节与 OSS 凭据彻底隔离。架构总览可信发布者与共享上传器上传器脚本两个可信发布者任务Web-shell Visuals Publish与qwen-triage.yml中的 verify 发布步骤都配置现有的ossutil客户端并调用同一个上传器脚本 scripts/upload-aliyun-oss-assets.js。该脚本的关键行为三次重试MAX_UPLOAD_ATTEMPTS 3首次失败后退避INITIAL_BACKOFF_MS 2000ms之后每次翻倍2s → 4s全部失败后以fail()退出非零public-readACL通过ossutil cp的--acl public-read参数发布对象保证评论里的图片 URL 可公开访问CLI 参数--bucket、--configossutil 配置文件路径、--prefix目标对象前缀尾部/会被剥离、以及一个或多个ASSET本地文件路径--help/-h可查看用法对象键生成${prefix}/${path.basename(asset)}即上传者只负责把每个文件的 basename 拼接到前缀之后。可选的单次尝试超时防黑洞连接脚本默认不对 ossutil 调用做超时限制OSS_UPLOAD_ATTEMPT_TIMEOUT_MS未设置时为 0保持发布同步任务不设限的历史行为。但 PR 发布任务运行在 10 分钟作业上限内一个黑洞 socket接受连接但永不推进的上传会把整个报告/评论拖死因此工作流通过环境变量OSS_UPLOAD_ATTEMPT_TIMEOUT_MS显式开启超时工作流中设置为120000120 秒/次超时后以SIGKILL终止该次尝试并视其为可重试的尝试失败error.code ETIMEDOUT不会作为 spawn 错误抛出最坏预算单个图片 3 次尝试 × 120s 两次退避 ≈ 6.1 分钟仍落在 10 分钟作业上限内。幂等去重与同一 PR 的发布串行化Web-shell Visuals Publish工作流通过concurrency配置按源仓库 分支workflow_run.head_repository.full_namehead_branch为同一个 PR串行化发布避免旧 run 覆盖新 run 的标记评论不同 PR包括恰好共享main分支名的 fork则分到不同分组、并行发布且cancel-in-progress: false保证不取消进行中的发布。不可变对象前缀为什么三个维度都要进键Web Shell 预览前缀pr-assets/web-shell-visuals/pr/head-sha/run-id/run-attempt/例如工作流中实际拼出的ASSET_PREFIXpr-assets/web-shell-visuals/${PR}/${RUN_HEAD_SHA}/${RUN_ID}/${RUN_ATTEMPT}见 .github/workflows/web-shell-visuals-publish.yml。设计上保证同一个对象键永远不会被写两次每个维度都有不可替代的作用head SHA把 URL 绑定到它描绘的代码版本——评论图片的字节所对应的就是该提交run id防止同一 head 的重跑覆盖掉已发布评论所引用的对象run attemptrun 序号单独只有 run id 不够run id 在重跑尝试之间是稳定的只有 attempt 递增所以 attempt 段还能把维护者对同一 workflow run 的手动重跑挡在上一 attempt 的键之外。之所以必须如此严格是因为GitHub 通过缓存型图片代理camo proxy按 URL 缓存评论图片如果复用键评论者会在字节已被改变的 URL 上继续看到上一轮 run 的截图。旧的 Git 方案靠 raw URL 中每次 run 唯一的提交 SHA 免费获得了这一属性迁移到 OSS 后必须把每次发布写一个从未被服务过的前缀作为显式约束落实。/verify沙箱报告前缀pr-assets/verify/prpr-run-id-run-attempt/实际实现中拼装为pr-assets/verify/pr${PR_NUMBER}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}见 .github/workflows/qwen-triage.yml。verify 通道用run-id-attempt的组合维持同样的不可变性。校验与降级fail-safe行为/verify通道沿用既有上限至多 8 张 PNG、每张至多 2 MiB。实现上用find -size -2097153c做字节精确上限-2M单位会先把文件大小向上取整到整 MiB把文档化的 2 MB 上限静默变成 1 MiB因此必须用字节单位再用head -8截取最多 8 张见 .github/workflows/qwen-triage.ymlPNG magic bytes 校验head -c 8与89504e470d0a1a0a比对确保字节确实匹配声称的类型——ossutil 会根据键的扩展名推导对象的Content-Type因此.png白名单正是让对象以图片类型被服务的关键约束点文件名净化tr -cd a-zA-Z0-9._-清洗并截断至 80 字符拒绝空名、以.开头、非.png结尾及净化后重名的文件重名会静默覆盖并渲染两次托管失败降级上传失败时/verify报告退化为纯文本评论并输出本地前置条件诊断ossutil 二进制是否缺失、配置文件是否存在以区分工具/凭据缺口与瞬时上传抖动Web Shell 预览则刷新评论但不带损坏的图片 URLhostingFailed状态。与 untrusted PR 字节的隔离发布者任务在基础仓库上下文中运行绝不向执行 PR 代码的作业暴露 OSS 凭据它们只消费有界、已校验的图片制品。以Web-shell Visuals Publish为例它通过workflow_run触发permissions仅声明actions: read评论用CI_BOT_PAT发布OSS 用自己的仓库 secretscheckout 只做 sparse-checkoutpackage.json、发布脚本与上传器且把 ref 显式 pin 到github.sha从步骤层面保证绝不 checkout/运行 PR 代码。来自 PR 的pr.txt被视为不可信输入先净化再与认证的 head SHA、repo、branch 三重绑定校验validate_pr并在上传前后与写评论前反复执行gate压缩 validate→write 的 TOCTOU 窗口。Bucket 选择与凭据边界解析顺序两个发布者都把目标解析为ALIYUN_OSS_PR_ASSETS_BUCKET → ALIYUN_OSS_BUCKET → qwen-code-assets公共基础 URL 由胜出的 bucket 推导或通过ALIYUN_OSS_PR_ASSETS_PUBLIC_BASE_URL显式设置。工作流中的实际表达式两个发布者一致见 .github/workflows/web-shell-visuals-publish.ymlALIYUN_OSS_BUCKET: ${{ vars.ALIYUN_OSS_PR_ASSETS_BUCKET || vars.ALIYUN_OSS_BUCKET || qwen-code-assets }} ALIYUN_OSS_PUBLIC_BASE_URL: ${{ vars.ALIYUN_OSS_PR_ASSETS_PUBLIC_BASE_URL || (vars.ALIYUN_OSS_PR_ASSETS_BUCKET vars.ALIYUN_OSS_PUBLIC_BASE_URL) || format(https://{0}.oss-cn-hangzhou.aliyuncs.com, vars.ALIYUN_OSS_PR_ASSETS_BUCKET || vars.ALIYUN_OSS_BUCKET || qwen-code-assets) }}两点设计意图值得注意默认行为不变未设置任何变量时解析回当前共享 bucketqwen-code-assets独立开关的意义PR 证据是不可信、PR 派生的内容而共享 bucket 同时服务 release、desktop、live-host 下载只设置一个变量即可把它们分开无需改动工作流。默认公共 URL 从最终解析出的 bucket 推导是为了防止只覆盖两个变量之一时评论链接指向另一个 bucket 上的 404 或陈旧对象。若通过ALIYUN_OSS_ENDPOINT跨区域覆盖也必须同步覆盖ALIYUN_OSS_PUBLIC_BASE_URL因为默认 host 固定为杭州oss-cn-hangzhou.aliyuncs.com。凭据边界不缩小密钥只隔离职责该开关不会收窄凭据这些任务持有与 release 同步相同的 OSS key。将专用 RAM 用户按pr-assets/前缀做权限收敛属于本 PR 之外的运维工作——这正是目标被设计成变量而非常量的原因为未来按前缀做最小权限授权留出挂载点。自动评审的第三条图片路径qwen review publish-assets除上述两个 OSS 发布者外自动化评审工作流还有第三条独立的图片路径CLI 命令qwen review publish-assets实现见 packages/cli/src/commands/review/publish-assets.ts。命令语义与约束该命令仍可用于明确指定的资产仓库QWEN_REVIEW_ASSETS_REPOowner/repo必须由用户手工设置绝不由模型自选未设置则不发布、以退出码 3 结束。文件通过 GitHub Contents API 落到资产仓库的pr-assets/pr-review分支上无本地 clone、无 SSHURL 固定到最终提交 SHA保证已发布评论的证据即使分支后续移动也保持不可变。自指向目标被拒绝迁移后的工作流拒绝指向被评审仓库自身的目标设定目标未设置或指向自身时降级为纯文本评论 可下载的 run 制品指向外部图片托管仓库仍然受支持。兼容性与遗留清理旧评论与历史分支已有 PR 评论继续保留其 Git 托管的 URL不强制迁移。PR 关闭清理工作流 .github/workflows/web-shell-visuals-cleanup.yml 继续删除历史pr-assets/*引用但被迁移的工作流不再产生任何新引用。清理任务在基础上下文pull_request_target运行只按名字删除三类历史分支绝不 checkout 或运行 PR 代码pr-assets/web-shell-visuals-PR_NUMBER pr-assets/PR_NUMBER-verify pr-assets/PR_NUMBER-review删除逻辑刻意不用set -e单个分支缺失或删除失败不应中断其余分支且大多数 PR 两个分支都不产生是常态。OSS 对象保留策略OSS 对象的保留策略有意放在工作流之外pr-assets/前缀可以配置 bucket 生命周期规则lifecycle rule来统一过期清理不需要改动仓库历史或克隆行为——这正是对象托管与仓库彻底解耦的收益。验证矩阵如何证明迁移正确设计文档给出了四项验收手段均能在仓库中找到对应实现工作流级/verify图片托管 harness针对一个 fake OSS 上传器执行覆盖合法图片、被拒图片、重名、精确大小边界、首次发布与上传失败VERIFY_ASSETS_UPLOADER环境变量即为此测试缝而设见 .github/workflows/qwen-triage.yml断言两个发布者工作流都调用共享上传器、且不含 Git 图片推送路径对应 .github/scripts/web-shell-visuals-publish.test.mjs 中workflow hosts visuals on OSS without writing Git refs测试——断言工作流匹配scripts/upload-aliyun-oss-assets.js、匹配pr-assets/web-shell-visuals/${PR}/${RUN_HEAD_SHA}/${RUN_ID}/${RUN_ATTEMPT}键模板、匹配ALIYUN_OSS_PUBLIC_BASE_URL与RUN_ATTEMPT注入断言自动化评审工作流不能把本仓库作为图片分支目标对应qwen review publish-assets的自指向拒绝逻辑共享上传器单元测试与工作流解析器测试上传器的可测表面parseUploadArgs、resolveAttemptTimeoutMs、uploadAssets被显式导出见 scripts/upload-aliyun-oss-assets.js配合web-shell-visuals-publish.test.mjs对sanitizeName、classifyMagic、selectImages、buildComment等纯函数的覆盖——这些函数正是消费不可信 PR 输出、此前完全无测试覆盖的部分一个 shell 净化 bug 曾给每个文件名追加_并静默产生空预览。实施要点速览维度取值 / 行为共享上传器scripts/upload-aliyun-oss-assets.js3 次尝试、2s 起指数退避、public-readACL单次尝试超时环境变量OSS_UPLOAD_ATTEMPT_TIMEOUT_MSPR 任务设为 120000msrelease 同步默认不设限Web Shell 预览前缀pr-assets/web-shell-visuals/pr/head-sha/run-id/run-attempt/verify 报告前缀pr-assets/verify/prpr-run-id-run-attempt/verify 图片上限至多 8 张 PNG、每张 ≤2 MiB字节精确、PNG magic bytes 校验、净化文件名bucket 解析ALIYUN_OSS_PR_ASSETS_BUCKET→ALIYUN_OSS_BUCKET→qwen-code-assets公共 URLALIYUN_OSS_PR_ASSETS_PUBLIC_BASE_URL默认按 bucket 推导杭州区域评审 CLIqwen review publish-assets仅写入QWEN_REVIEW_ASSETS_REPO拒绝指向被评审仓库遗留清理PR 关闭时删除pr-assets/web-shell-visuals-pr、pr-assets/pr-verify、pr-assets/pr-review三类历史分支对象保留由 bucket 生命周期规则管理工作流不再负责总结这次迁移的核心价值在于把PR 证据的存储从仓库的版本历史中剥离通过 head SHA、run id、run attempt 三元素构造不可变对象键在 GitHub camo 代理按 URL 缓存的现实约束下保证了评论截图的字节一致性通过可信发布者与 untrusted 字节的严格隔离、字节级校验与纯文本降级维持了与旧 Git 方案同等的安全与可用性而独立的 bucket 变量则为未来按pr-assets/前缀收敛 RAM 权限留下了明确的运维挂载点。对维护者而言理解这套前缀与回退规则是排查评论图片不更新图片 404或克隆体积问题的第一把钥匙。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考