ci(desktop): exercise desktop packaging on a schedule - #11519
Conversation
The desktop app bundles a Qwen Code runtime built from this repository's dist tree, so a change anywhere in the CLI can break desktop packaging. Nothing exercised that path outside a manual release dispatch: the only job that touches it compiles the Tauri crate and runs its tests, and it runs solely when the desktop package itself changes. Desktop release 0.2.3-preview.0 is what that costs. A CLI dependency added two weeks earlier had put a native library in the bundled runtime that the AppImage bundler cannot process, and release day was the first time anyone found out — on three of four platforms at once, with the publish step already queued behind them. Make the release workflow callable and add a scheduled caller that runs it as a dry run, so a packaging regression surfaces the day it lands. A dry run leaves the signing steps out, so it covers building the runtime, the installers and the packaged smoke tests, but not macOS notarization or Windows Authenticode.
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical — and I checked the structural half rather than taking it on faith. The 0.2.3-preview.0 incident is named with a concrete failure mode (a renderer dependency put a native library into the bundled runtime that the AppImage bundler cannot process; three of four platform jobs failed on release day). Independently of that, the Direction: aligned. This closes a coverage hole by reusing the pipeline that already exists instead of adding a parallel one, which is the right shape: a re-implementation of the packaging steps would drift away from the very thing it is supposed to be protecting. CI-only, no user surface, so no CHANGELOG signal applies. Size: not applicable — Approach: scope feels right and the diff is genuinely minimal — Risk: no elevated risk signals — the Stage 1e path patterns match nothing here (no source files at all). Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 是已观测到的问题,不是理论性加固——而且结构性的那一半我自己去核实了,没有直接采信描述。0.2.3-preview.0 这次事故有具体的失效方式(一个渲染器依赖把原生库带进了自带运行时,AppImage 打包器无法处理,发布当天四个平台 job 中有三个失败)。除此之外, 方向: 对齐。它靠复用已有流水线来补覆盖,而不是新加一条平行的——这是对的形态:把打包步骤重新实现一遍,恰恰会与被保护的对象逐渐脱节。纯 CI 改动,无用户可见界面,因此不涉及 CHANGELOG 信号。 规模: 不适用——只涉及 方案: 范围合理,diff 也确实是最小的—— 风险: 无升级风险信号——Stage 1e 的路径模式在这里没有命中(完全没有源码文件)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewI read the whole 736-line callee, because converting a release workflow into a reusable one is exactly where a dry run can quietly stop being dry. The claims in the description hold up: It cannot publish. It holds no signing material. The caller deliberately omits It cannot collide with a real release. The callee's concurrency group is It is not vacuous. This was the thing I most wanted to confirm, since a green scheduled run that tests nothing is worse than no run. Tracing the dry-run path through Blocker —
|
| Check | Conclusion |
|---|---|
Lint & Static (ubuntu-latest, Node 22.x) |
❌ failure |
Test (ubuntu-latest, Node 22.x) |
❌ failure |
web-shell E2E Smoke (ubuntu-latest, Node 22.x) |
🚫 cancelled |
Classify PR |
✅ success |
Desktop Shell (ubuntu-22.04) |
✅ success |
Desktop Shell (windows-2022) |
✅ success |
Integration Tests (no-AK, No Sandbox) |
✅ success |
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。
Not verified: that the scheduled run actually goes green end to end — and this one cannot be settled by reading. A manual workflow_dispatch dry run receives repo secrets; the scheduled/workflow_call run receives none, and that absence is new. tauri.conf.json sets createUpdaterArtifacts: true and the artifact-collection step expects .sig files, so the combination "updater artifacts enabled + empty signing key + --no-sign" has never run without secrets before. If Tauri objects to a missing updater key even under --no-sign, the scheduled run fails where a manual dry run passed. The remedy is the one already in your test plan: dispatch the new workflow once against main before merging. Neither @qwen-code /verify nor @qwen-code /tmux can settle this — one is an A/B harness against the base build, the other drives the TUI, and neither runs a Tauri packaging workflow — so I am naming the dispatch rather than a lane that would produce noise.
中文说明
代码审查
我把 736 行的被调用 workflow 完整读了一遍,因为把发布 workflow 改成可复用正是"dry run 悄悄变得不 dry"的地方。描述里的几条声明都成立:
它不可能发布。 publish 的门槛是 inputs.dry_run == false,sync-oss 的门槛是 github.event_name == 'workflow_dispatch' 加上 dry_run == false 再加 draft/prerelease 同时为 false。在 workflow_call 且 dry_run: true 的情况下两个 job 都跳过——不建 git tag、不建 GitHub release、不写 OSS 镜像。publish 的 contents: write 是 job 级的,跳过后根本不会用到,而调用方自己的 permissions: contents: read 也是最小权限。
它不持有任何签名材料。 调用方刻意没有写 secrets: inherit,所以被调用方里每个签名 secret 都解析为空。所有消费 secret 的步骤都 gated 在 dry_run == false 上——updater key、macOS 证书、公证、Windows 证书、vendor 二进制签名、校验和刷新、两个签名校验。唯一的例外是 Build desktop installers,它未 gated 地引用了 TAURI_SIGNING_PRIVATE_KEY,但在 DRY_RUN=true 时走 --no-sign 分支,因此并不需要那个值。这个性质是靠结构保证的,而不是靠约定,很好。
它不会与真实发布撞车。 被调用方的并发组是 desktop-release-${{ inputs.dry_run && inputs.version || 'publish' }},所以每日运行落在 desktop-release-0.0.0-packaging-check,而发布运行落在 desktop-release-publish——互不相交。cancel-in-progress: false 意味着它排队,而不会取消任何东西。
它不是空转。 这是我最想确认的一点,因为一个"绿了但什么都没测"的定时运行比没有运行更糟。沿着 build 的 dry-run 路径追下去:build:runtime → desktop tests → test:release → smoke:runtime → tauri build --no-sign → 各平台的 smoke:packaged → 产物收集,全都仍然执行。它会走到 AppImage 打包器,也就是 0.2.3-preview.0 当时出问题的确切步骤。0.0.0-packaging-check 也能通过 prepare 的 SemVer 正则,以及 prerelease: true 所要求的预发布后缀规则。
阻塞项——Lint & Static 是红的,而且是本 PR 造成的
上方引用了原始日志。workflow 体积棘轮要求 .github/workflows/ 下每个文件都在 .size-baseline 里有记录,而新增的 workflow 没有。这不是 main 侧的陈旧噪声:check-workflow-size.sh 在文件与本 PR base 逐字节相同时会降级成 ::warning::,而这里输出的是 ::error::,且 WORKFLOW_SIZE_BASE_SHA 已解析为 0b2ba2d2。文件是本 PR 新增的,所以这条记录归本 PR。
一行即可修复——在 .github/workflows/.size-baseline 中加入 1634 desktop-packaging-check.yml,按字母序正好放在已有的 desktop-release.yml 那条前面。我也核对了被调用文件是否需要一起改:不需要,它从 31677 增长到 32378 字节,超出 701,在 4096 字节的 GROWTH_ALLOWANCE 之内。baseline 没有需要同步的 vitest 镜像,所以这一行就是完整的修复。
非阻塞
sync-oss 现在无法通过新入口到达。 它的 if 钉死了 github.event_name == 'workflow_dispatch',在 workflow_call 下为 false。今天无害——唯一的调用方传的是 dry_run: true,本来就会跳过——但正是这个 diff 让 workflow_call 成了一个受支持的入口。将来若有调用方通过它做真实发布,会创建 GitHub release 却静默跳过 OSS 镜像,而且没有任何东西会报错。建议要么在该 job 上加注释说明它是刻意仅限 dispatch,要么直接去掉 event-name 这一条——dry_run/draft/prerelease 已经足够把关了。
平台集合。 如 Stage 1 所述:这个 workflow 要拦的自带运行时回归与架构无关,所以 macOS arm64 和 x64 基本上是在两个最贵的 runner 上重复测同一份 dist/。Linux 每日 + macOS 每周,或者每日只跑一个 macOS 架构,能用少得多的分钟数保住大部分覆盖。另有一点小的:每天四个产物、retention-days: 14,稳态下大约会同时存在 56 组安装包产物。
测试证据
无人值守 CI 运行——我没有构建或执行本 PR 的任何代码。以下证据是所审查 commit 上本 PR 自己的 check-run,通过 API 读取。
Lint & Static 为 failure,由本 PR 造成,日志摘录见上。Desktop Shell 两个平台为 success,但请注意它们不构成本次改动的证据——那个 job 只编译 Tauri crate,明确不构建自带运行时。Test (ubuntu-latest) 与 Integration Tests (no-AK, No Sandbox) 在审查时仍在进行中;其余为跳过的 bot 编排 job。这里没有任何一项属于既有的基础设施噪声。上方的表格即为真实 check 名称与结论,由 finalize 任务在 CI 跑完后就地更新。
未验证:定时运行是否真的能端到端跑绿——而这一条无法靠阅读确定。手动 workflow_dispatch 的 dry run 能拿到仓库 secrets,而定时的 workflow_call 运行一个都拿不到,这个"缺失"是新出现的。tauri.conf.json 里 createUpdaterArtifacts: true,且产物收集步骤期待 .sig 文件,所以"启用 updater 产物 + 签名 key 为空 + --no-sign"这个组合此前从未在无 secrets 的情况下跑过。如果 Tauri 即便在 --no-sign 下也要求 updater key 存在,定时运行就会在手动 dry run 能过的地方失败。补救办法正是你测试计划里已经写的那条:合并前针对 main 手动触发一次新 workflow。@qwen-code /verify 和 @qwen-code /tmux 都解决不了这个问题——前者是针对 base 构建的 A/B 装置,后者驱动 TUI,两者都不会跑 Tauri 打包 workflow——所以我点名的是手动触发,而不是一条只会产生噪声的 lane。
— Qwen Code · qwen3.8-max-2026-09-02
Reviewed at 806a295b2b30dfe2de23170c7a996ff787199706 · re-run with @qwen-code /triage
|
Confidence: 2/5 — the design is sound and I could verify nearly all of it statically, but it cannot merge as-is: it trips the repo's own workflow-size ratchet and leaves Stepping back: this is the rare CI PR where the motivation survives scrutiny. I went in expecting to argue that a daily four-platform packaging run is a lot of runner time for a hypothetical, and instead found a named incident plus a structural gap I could confirm from the existing job's own comment — The approach also matches what I would have proposed from the title and motivation alone: make the release workflow callable, add a thin scheduled caller that passes a dry run, and — the part that matters — do it without So the block is narrow and mechanical. One line in Two things I'd genuinely like a human decision on, neither blocking:
And one thing nobody can settle by reading: the scheduled run receives no secrets where a manual dry run receives all of them, and Requesting changes for the baseline entry; everything else is discussion. @yiliang114 中文说明Confidence: 2/5 —— 设计是站得住的,绝大部分我都能靠静态审查确认,但它现在不能合并:它触发了本仓库自己的 workflow 体积棘轮,让 退一步看:这是少见的、动机经得起推敲的 CI PR。我一开始是准备论证"每天跑四个平台的打包,为的是一个假想问题,代价太大",结果发现既有具名事故,又有一个我能从既有 job 自己的注释里确认的结构性缺口—— 方案也与我仅凭标题和动机就会提出的做法一致:把发布 workflow 改成可被调用,加一个很薄的定时调用方传入 dry run,并且——最关键的一点——不写 所以阻塞点很窄、也很机械。 有两件事我确实希望由人来决定,都不是阻塞项:
还有一件事是谁都无法靠阅读确定的:定时运行拿到的 secrets 为零,而手动 dry run 能拿到全部,且 因为 baseline 这一条记录而 request changes;其余都是讨论。@yiliang114 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Needs one line before this can merge — Lint & Static is red on a gate this PR trips:
##[error].github/workflows/desktop-packaging-check.yml has no entry in
.github/workflows/.size-baseline. Add '1634 desktop-packaging-check.yml'
so its growth is tracked.
Add 1634 desktop-packaging-check.yml to .github/workflows/.size-baseline (alphabetically just before desktop-release.yml). That is the whole fix — desktop-release.yml grew 701 bytes, inside the 4096-byte allowance, so it needs no bump.
Everything else is discussion, not blocking, and it is in my notes above. The design itself I could verify statically and it holds: the dry run cannot publish, holds no signing secrets, cannot collide with a real release's concurrency group, and is not vacuous — it does reach the AppImage bundler and the packaged smoke tests. The one thing worth doing before merge is dispatching the new workflow once against main, since a scheduled run receives no secrets where a manual dry run receives all of them, and createUpdaterArtifacts: true has never been exercised with an empty signing key.
中文说明
合并前需要加一行——Lint & Static 是红的,触发它的正是本 PR:原始报错见上。
在 .github/workflows/.size-baseline 中加入 1634 desktop-packaging-check.yml(按字母序放在 desktop-release.yml 前面)。这就是完整的修复——desktop-release.yml 增长了 701 字节,在 4096 字节的额度之内,因此不需要一起改。
其余都是讨论、不阻塞,详见上方评论。设计本身我能静态确认,而且成立:dry run 不可能发布、不持有签名 secrets、不会与真实发布的并发组撞车,也不是空转——它确实会走到 AppImage 打包器和打包后的冒烟测试。合并前值得做的一件事是针对 main 手动触发一次新 workflow,因为定时运行拿不到任何 secrets,而手动 dry run 能拿到全部,且 createUpdaterArtifacts: true 从未在签名 key 为空的情况下跑过。
— Qwen Code · qwen3.8-max-2026-09-02
The workflow size ratchet requires every workflow file to carry a recorded byte size, and a file it has never seen fails both the lint gate and its vitest counterpart. Register the new check at its current size; desktop-release.yml stays inside its growth allowance and needs no bump. Merging main also brings in the macOS and Linux packaging fixes, so this branch now builds on the state the scheduled dry run will actually meet.
|
Fixed — On the point you flagged as worth checking before merge — That leaves one thing genuinely unexercised until this merges: a run of the reusable workflow with no secrets passed at all. Dispatching the check once against 中文说明已修复—— 关于你提出的合并前值得确认的那一点—— 因此在合并之前真正无法验证的只剩一件事:以完全不传 secrets 的方式运行这个可复用 workflow。合并后针对 |
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Approved at head 9c6c2492.
Required CI is green here — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox) and web-shell E2E Smoke plus both Desktop Shell lanes completed successfully; only review-pr is still running.
The one blocking point from the earlier REQUEST_CHANGES is fixed: .github/workflows/.size-baseline now carries 1634 desktop-packaging-check.yml, which is byte-exact against the new file at this head (wc -c on the blob at this commit returns 1634), placed alphabetically before desktop-release.yml; the growth on desktop-release.yml stays inside the 4096-byte allowance, which is why Lint & Static is now green.
I re-derived the safety properties the design leans on rather than trusting the comment:
- Every condition reads
inputs.*, nevergithub.event.inputs.*, so aworkflow_callinvocation sees the same values a dispatch does — and theworkflow_callinput set is identical toworkflow_dispatch(all seven), with the caller supplying each one, so nothing can arrive silently undefined. - It cannot publish: the publish job is gated on
inputs.dry_run == false, and the downstream job additionally requiresgithub.event_name == 'workflow_dispatch', which a called workflow never satisfies. - It cannot collide with a real release: the concurrency group is
desktop-release-${{ inputs.dry_run && inputs.version || 'publish' }}, so the check lands indesktop-release-0.0.0-packaging-checkwhile a publish usesdesktop-release-publish. - Not holding secrets is genuine, not just a claim: the caller omits
secrets: inherit, and 'Build desktop installers' appends--no-signwhenDRY_RUNis true, socreateUpdaterArtifacts: trueintauri.conf.jsondoes not needTAURI_SIGNING_PRIVATE_KEYon this path; the key requirement itself sits behindif: inputs.dry_run == false. Forks are excluded by the repository guard on the calling job.
No new Critical found. Two things I could not verify from the diff, both already flagged by the author: a workflow can only really be checked by running it, so the first manual dispatch against main is the proof this is not vacuous, and the cost is four ~25-minute jobs daily with two on macOS runners.
Note for whoever merges: my approval does not clear GitHub's aggregate verdict while the older CHANGES_REQUESTED (filed at 806a295b) stands unresolved — it needs a re-run of the reviewer at this head.
chiga0
left a comment
There was a problem hiding this comment.
No blocking findings. Approval blockers: none.
Checked:
- Input contract (caller → callee): all 7 required
workflow_callinputs declared indesktop-release.ymlare passed bydesktop-packaging-check.yml; types match (booleaninputs passed as literaltrue/false). - Version validation:
0.0.0-packaging-checkpasses the semver regex in thepreparejob (^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$) and the prerelease-suffix check (^[0-9]+\.[0-9]+\.[0-9]+-). - Signing-step isolation: all signing/certificate steps in
desktop-release.ymlare gated oninputs.dry_run == false(lines 251, 263, 299, 331, 384, 437). No signing step runs whendry_run: true. publishjob gated oninputs.dry_run == false— will not run.sync-ossjob gated ongithub.event_name == 'workflow_dispatch'— will not fire when called viaworkflow_call.- Smoke tests run unconditionally on all three platforms — consistent with PR description.
- Schedule fallback:
inputs.qwen_code_ref || 'main'correctly handles empty input when the trigger isschedule. secrets: inheritomitted: correct — no signing step runs during a dry run, so no secrets are needed..size-baselineentry:1634 desktop-packaging-check.ymlis present in the diff, alphabetically ordered beforedesktop-release.yml.
Cross-check: One prior review (qwen-code-ci-bot, CHANGES_REQUESTED) requested the .size-baseline entry — that entry is now in the diff, resolving the blocker. The bot also noted that a manual dispatch before merge would confirm the createUpdaterArtifacts: true path with an empty signing key; that is a testing suggestion, not a code defect.
Not covered: execution rung (CI-only change; no local runtime to drive). This does not block approval — the workflow's correctness is verifiable from the static YAML contract, and the execution gap was already disclosed by the PR author.
Reviewed with AI assistance.
|
Released in v0.23.3. |
What this PR does
Makes the desktop release workflow callable and adds a scheduled caller that runs it as a dry run once a day, so the desktop packaging path is exercised continuously instead of only when someone dispatches a release.
Why it's needed
The desktop app bundles a Qwen Code runtime built from this repository's dist tree, so a change anywhere in the CLI can break desktop packaging. Nothing exercised that path outside a manual release dispatch: the only continuous integration job that touches the desktop package compiles the Tauri crate and runs its tests, and it runs solely when that package itself changes. A dependency added on the CLI side is invisible to it.
Desktop release 0.2.3-preview.0 is what that costs. A renderer dependency added two weeks earlier had put a native library into the bundled runtime that the AppImage bundler cannot process, and release day was the first time anyone found out — three of the four platform jobs failed at once, with the publish step queued behind them, and the failure had to be diagnosed under release pressure rather than next to the change that caused it.
A dry run builds the runtime, the installers and runs the packaged smoke tests on all four targets, which is exactly the coverage that was missing. It leaves the signing steps out, because those are gated on a real publish, so it does not cover macOS notarization or Windows Authenticode — the workflow comment says so rather than leaving the gap to be discovered.
Reviewer Test Plan
How to verify
Dispatch the new check manually and confirm it starts the release workflow's four build jobs against
main, that each one reaches its packaged smoke test, and that the publish and mirror jobs are skipped. The release workflow itself should behave exactly as before when dispatched directly — the new trigger adds inputs, it does not change any existing one.To confirm it catches the class of regression it exists for, dispatch it against a ref that carries a broken bundled runtime and check that the Linux job fails in the installer step rather than reporting success.
Evidence (Before & After)
N/A — CI configuration, no user-visible surface.
Tested on
Both files were checked for YAML validity and formatting; the workflow itself can only be verified by running it, which needs a dispatch on this repository.
Environment (optional)
N/A
Risk & Scope
Linked Issues
Related to #8092.
中文说明
这个 PR 做了什么
把桌面发布 workflow 改成可被调用的,并新增一个定时调用方,每天以 dry run 的方式跑一次,让桌面打包链路持续得到验证,而不是只有在有人手动触发发布时才跑一次。
为什么需要
桌面应用打包的运行时来自本仓库的 dist 产物,因此 CLI 侧任何一处改动都可能打断桌面打包。而在手动发布之外,没有任何流程覆盖这条链路:唯一涉及桌面包的持续集成 job 只编译 Tauri crate 并跑它自己的测试,而且仅在该包自身发生改动时才触发。CLI 侧新增的依赖对它是不可见的。
desktop 0.2.3-preview.0 就是代价。两周前引入的一个渲染器依赖,把一个 AppImage 打包器无法处理的原生库带进了自带运行时,而发布当天才第一次暴露出来——四个平台 job 中有三个同时失败,发布步骤还排在它们后面,问题只能在发布压力下排查,而不是在引入改动的当时。
一次 dry run 会在四个目标上构建运行时、构建安装包并跑打包后的冒烟测试,这正是此前缺失的覆盖。它不包含签名步骤(那些以真实发布为前提),所以不覆盖 macOS 公证和 Windows Authenticode——workflow 注释里写明了这一点,而不是留给后人踩。
评审验证计划
如何验证
手动触发新的检查 workflow,确认它针对
main启动了发布 workflow 的四个构建 job,每个都跑到打包后的冒烟测试,并且 publish 与 OSS 镜像两个 job 被跳过。直接触发发布 workflow 时行为应与之前完全一致——新触发器只是增加了入口,不改变任何已有入口。要确认它确实能拦住这类回归,可以针对一个自带运行时已损坏的 ref 触发它,检查 Linux job 是在安装包构建步骤失败,而不是报成功。
证据(Before & After)
N/A——CI 配置改动,无用户可见界面。
测试平台
两个文件都做了 YAML 合法性与格式检查;workflow 本身只能通过实际运行验证,这需要在本仓库上触发一次。
环境(可选)
N/A
风险与范围
关联 Issue
关联 #8092。