fix(ci): grant the packaging check the permissions its callee declares - #11532
Conversation
The scheduled packaging check failed to start. A caller must grant every permission the called workflow's jobs declare, and the release workflow's publish job declares `contents: write` while the OSS mirror declares `actions: read` — the check granted only `contents: read`. That validation runs when the workflow file is loaded, before any `if` is evaluated, so the two jobs being unreachable in a dry run does not exempt their permissions. The run died in two seconds with no job and no annotation, which is what a startup failure looks like. Grant both. Nothing here writes: the check hardcodes `dry_run` to true, which skips the publish and the mirror job on their own conditions.
|
|
|
Thanks — and thanks for chasing this the same day it broke. Template looks good ✓ Problem: observed, not theoretical. I checked run 34444598159 through the API rather than taking the description's word for it: Direction: squarely in scope. This is CI infrastructure reviving a currently-dead schedule, not new surface. No CHANGELOG reference applies, and none should. Size: not applicable — no core paths, 8 changed lines in one workflow file. Approach: the scope is right and I could not find a smaller correct fix. I wrote down what I would do before reading the diff, and it is the same two lines: the caller has to cover the union of what the callee's jobs declare, and that union is exactly One thing worth thinking about, not a blocker: this couples the two files silently. If Risk: no elevated risk signals — no high-risk paths matched. The residual risk is the one you already named: a scheduled workflow now declares Moving on to code review. 🔍 中文说明感谢提交,也感谢你当天就把这个问题追了下去。 模板完整 ✓ 问题:是已观测到的故障,不是理论性加固。我通过 API 核实了运行 34444598159,而不是只采信 PR 描述: 方向:完全在范围内。这是 CI 基础设施修复一个当前已失效的定时检查,不是新增能力。CHANGELOG 无对应条目,也不应有。 规模:不适用——未触及核心路径,单个 workflow 文件共 8 行改动。 方案:范围合适,我找不到更小且正确的修法。在读 diff 之前我先写下了自己的做法,结论是同样的两行:调用方必须覆盖被调用 workflow 各 job 声明的权限并集,而该并集恰好是 有一点值得考虑,但不是阻塞项:这让两个文件产生了无声的耦合。如果 风险:无升级风险信号——未匹配到高风险路径。剩余风险正是你已经点出的那个:一个定时 workflow 现在声明了 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewNo blockers. The fix is correct and I could not find a smaller one. What I checked, beyond the diff:
Two observations, neither blocking:
I skipped the sequence diagram and the changed-files table — one workflow file, no runtime flow reshaped. Neither would have earned its place. Test evidenceThis is an unattended CI run, so I executed nothing from the PR and read its own checks through the API instead. Names and conclusions below are GitHub-set metadata for commit Read this part carefully: a green CI here is not evidence the fix works. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The remaining Not verified: that the workflow now starts. The claim this PR makes is behavioural and neither sandboxed lane can settle it — 中文说明代码审查无阻塞项。这个修复是正确的,我也找不到更小的修法。 除 diff 之外我核实的内容:
两点观察,均非阻塞:
我跳过了时序图与变更文件表——只有一个 workflow 文件,没有重塑任何运行时流程,两者都不值得占用篇幅。 测试证据这是无人值守的 CI 运行,因此我没有执行 PR 中的任何代码,而是通过 API 读取它自身的检查结果。下表中的名称与结论是 GitHub 设定的元数据,对应提交 这部分请仔细看:此处 CI 全绿并不构成修复有效的证据。 (CI 结果表见上方英文部分,此处不重复。) 其余 未验证:该 workflow 现在能否启动。 本 PR 的主张是行为性的,而两个沙箱通道都无法判定—— — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — correct, minimal, and backed by a failure I could verify independently; my one reservation is a maintenance coupling that outlives this diff. Going back to what I wrote down before reading the code: the same two lines. That is the strongest signal I have here, because I could not construct a smaller correct fix and the alternatives are worse in ways worth stating. Splitting the release workflow into a build-only half plus a publish half would remove the awkward declarative grant entirely and is arguably the right long-term shape, but that is a substantial refactor of the release path to repair a broken cron — not this PR's job. Relaxing what What convinced me is that every load-bearing claim here was checkable, so I checked it rather than reading it. The cited run really is a The honest limit, and the reason this is 4 rather than 5: no amount of review or CI proves the run will now start, because GitHub resolves this at dispatch time against the default branch. I am approving a static argument I believe is complete, not an observed green run. I think that is the right trade — leaving a currently-dead daily packaging check broken until someone can prove the repair works would just keep it dark, and the downside if I am wrong is another two-second failure with no side effects, not a bad release. If I am maintaining this in six months, the comment block is what saves me: it is the thing that stops a cleanup pass from reverting Approval is deferred until CI lands green on 中文说明Confidence: 4/5 — 正确、最小化,且背后有一个我能独立核实的真实故障;我唯一的保留意见是一个会超出本 diff 生命周期的维护耦合。 回到我在读代码之前写下的方案:同样是这两行。这是我在此处能给出的最强信号——我构造不出更小且正确的修法,而替代方案的问题值得说清楚。把发布 workflow 拆成"只构建"与"发布"两半,可以彻底消除这种别扭的声明式授权,长期看也许才是正确形态;但那是为了修一个坏掉的 cron 而对发布路径做大规模重构,不该由本 PR 承担。而放宽 说服我的是:这里每一条关键主张都是可核查的,所以我核查了,而不是读一遍就接受。所引用的运行确实是 诚实的局限,也是这里是 4 分而非 5 分的原因:无论审查还是 CI,都无法证明这个运行现在能够启动,因为 GitHub 是在触发时针对默认分支做这项解析的。我批准的是一个我认为完备的静态论证,而不是一次已观察到的绿色运行。我认为这个取舍是对的——让一个当前已失效的每日打包检查继续坏着,直到有人能证明修复有效,只会让它一直暗着;而如果我判断错了,代价是再一次两秒的失败、没有任何副作用,而不是一次错误的发布。 如果六个月后由我维护这段代码,那段注释正是救我的东西:它是阻止某次清理把 批准已推迟到 CI 在 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- X1-1 caller/callee permission-superset contract unpinned — already reported (comments 5614193538, 5614243900)
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Not linted (tool limitation, not a blocker): .github/workflows/desktop-packaging-check.yml — actionlint embedded-shell source mapping is not yet supported.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
未检查(工具限制,非阻断):.github/workflows/desktop-packaging-check.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。
— qwen3.8-max via Qwen Code /review (v0.23.2)
The Test lane on this branch was failing on WebShellSidebar.brand and the daemon route-surface guard, neither of which this branch touches — both were red on main, at and before the commit this branched from. #11530 realigned those suites with shipped behavior; merge it in so the lane reports on this change rather than on inherited breakage.
chiga0
left a comment
There was a problem hiding this comment.
No blocking findings.
Approval blockers: none.
Triage: Scan — single CI workflow permissions fix, no production code changes.
What I checked:
-
Root cause:
desktop-packaging-check.ymlcallsdesktop-release.ymlviauses:. GitHub Actions checks the caller's permissions against all permissions declared by the callee's jobs when the file loads, before anyifis evaluated. So even thoughdry_run: trueskips bothpublish(which declarescontents: write) andsync-oss(which declaresactions: read), the caller still needs to grant them. -
The fix:
contents: read→contents: write+actions: read, matching exactly whatdesktop-release.yml's jobs declare. Verified:preparejob declarescontents: write,sync-ossjob declaresactions: read. -
Safety: The comment correctly explains that nothing here actually writes —
dry_runis hardcoded true and thepublish/sync-ossjobs are unreachable. The elevated permissions are necessary for the workflow to start, not for any write operation. -
Comment quality: The added 7-line comment block clearly explains the "why" (the file-load-time permission check vs.
if-evaluation-time) so the next reader doesn't wonder why a dry-run workflow needscontents: write.
Reviewed with AI assistance.
qqqys
left a comment
There was a problem hiding this comment.
Critical-only scan at 8f6f0519 — no blocking defect. Approving.
Single file, +7/-1: five comment lines plus a permission change in .github/workflows/desktop-packaging-check.yml. Because this grants contents: write on a workflow that runs on a nightly cron, I treated the escalation as the whole review and verified its necessity and its blast radius rather than reading the comment's justification.
Prior review history — nothing blocking
No REQUEST_CHANGES review has ever been filed here. The one review at 1701a220 reports a single finding graded sev: "S" with floor: "o" and no Critical, and the triage code-review stage records "No blockers. The fix is correct and I could not find a smaller one." That Suggestion — the caller's dry_run: true becoming load-bearing without a test pinning the reachability invariant — was acknowledged by the author and deferred to a named follow-up. Suggestions do not gate this review, so there is no historical blocking issue to confirm as fixed. The head has since moved to 8f6f0519, a merge of main; the reviewed delta is unchanged by it.
The grant is exactly the required superset, not an over-grant
The callee desktop-release.yml declares permissions: contents: 'read' at the top level (line 70), which alone would not justify this change. The need comes from its two job-level declarations:
publish:(line 593) →permissions: contents: 'write'(lines 601-602), and it genuinely writes —gh release create,gh release upload --clobber,gh release editat lines 656-722.sync-oss:(line 739) →permissions: actions: 'read'(lines 746-747).
Those are the only two permissions: blocks below the top level in the callee, and the two keys this PR adds are precisely those two values. Nothing is granted beyond what the callee declares, so the escalation is the minimum the caller/callee contract requires and the PR title's claim is accurate.
Every job that could exercise the write is unreachable from this caller
publishis gatedif: inputs.dry_run == false && github.repository == 'QwenLM/qwen-code'(line 595). The caller hardcodesdry_run: trueat line 50.sync-ossis gatedif: github.event_name == 'workflow_dispatch' && inputs.dry_run == false && inputs.draft == false && inputs.prerelease == false && github.repository == 'QwenLM/qwen-code'(line 741). The caller passesdry_run: true,draft: trueandprerelease: true, so this job is unreachable on every trigger of this workflow, including the nightly0 16 * * *cron and a manual dispatch.dry_runis not exposed as aworkflow_dispatchinput of the caller — the only input isqwen_code_ref(lines 19-24). So an operator cannot flip it tofalsethrough this workflow, which is what keeps the hardcoded literal from being an operator-reachable switch.
Two further limits bound the blast radius independently of the if gates: the caller passes no secrets: inherit (and says so at lines 43-44), so the callee never holds a signing or OSS credential on this path; and the caller's only job is gated on github.repository == 'QwenLM/qwen-code' (line 42), so it does not run on forks. clobber: false is also passed.
No consumer or gate breaks
Nothing in scripts/tests/, packages/ or any other workflow references desktop-packaging-check.yml, so no test pins the previous contents: 'read' and the change cannot turn a suite red; .github/workflows/.size-baseline carries an entry for desktop-release.yml but none for this file, so no size gate applies either. desktop-release.yml itself is untouched, and desktop-packaging-check.yml is the only workflow that calls it, so no second caller inherits a changed contract. The YAML is well-formed — two keys under a top-level permissions: mapping, both valid GitHub permission names and values.
CI
Classify PR, Desktop Shell (ubuntu-22.04), Desktop Shell (windows-2022), Integration Tests (no-AK, No Sandbox), assign, authorize, label and Remind on force-push are green. Lint & Static, Test (ubuntu-latest, Node 22.x) and review-pr were still in progress at publish time; per this gate pending checks are not a blocker and none of the completed checks is attributable to this diff. The one lane that would actually exercise the changed file — the packaging check itself — runs on cron and dispatch, not on a pull request, so its next scheduled run is the real confirmation.
yiliang114
left a comment
There was a problem hiding this comment.
Self-review at head 8f6f0519 (GitHub does not let me approve my own PR). No blocking finding. The grant is correct, minimal, and provably inert on every reachable path. Two notes below, one of which corrects a claim in the description.
Verified against the callee rather than against the description
I enumerated every job-level permissions: block in desktop-release.yml instead of trusting the summary:
| callee job | declared permissions | reachable from this check? |
|---|---|---|
prepare (:82) |
none — inherits the callee's top-level contents: 'read' |
yes |
build (:153) |
none — same | yes |
publish (:593) |
contents: 'write' (:601-602) |
no — if: inputs.dry_run == false && …, and the caller hardcodes dry_run: true |
sync-oss (:739) |
actions: 'read', contents: 'read' (:746-748) |
no — four independent exclusions, below |
So the union the caller must cover is contents: write + actions: read + contents: read, and the new block grants exactly actions: 'read' + contents: 'write' — complete, with write subsuming the read. Nothing is missing, so the load-time comparison that killed the run should now pass.
sync-oss is worth spelling out because it is the source of the actions: 'read' half and it is excluded four separate ways: its gate is github.event_name == 'workflow_dispatch' && inputs.dry_run == false && inputs.draft == false && inputs.prerelease == false && github.repository == 'QwenLM/qwen-code', and the caller passes dry_run: true, draft: true, prerelease: true — while a schedule run is not workflow_dispatch in the first place. It is also itself a nested reusable call (uses: './.github/workflows/sync-desktop-to-oss.yml'), which is why its declared permissions propagate up to this caller at all.
One defence-in-depth property the new comment does not mention, and should. Because the callee's top-level permissions is contents: 'read', the prepare and build jobs — the only ones this check actually runs — inherit read-only and cannot consume the write grant even if their own gating changed later. The write is consumable solely by publish, which is unreachable here. So the grant is inert twice over: unreachable by condition, and unheld by any job that runs. A future editor who flips dry_run or adds a caller job should be able to see that from the file alone; right now they can only see the dry_run half.
Also confirmed the diagnosis rather than assuming it: run 34444598159 is conclusion: startup_failure, created_at 06:18:05Z → updated_at 06:18:07Z, run_attempt: 1, and its jobs list is empty — the 2-second, zero-job, zero-annotation shape the description claims.
The description overstates what cannot be verified pre-merge
Workflow permission resolution only happens on GitHub's side, so this can only be confirmed by dispatching after merge.
That is too strong. This branch is in-repo (QwenLM:fix/packaging-check-permissions, cross_repo: false), and the file at that ref is blob cf2c37a47b — matching the diff's post-image — so workflow_dispatch accepts it:
gh workflow run desktop-packaging-check.yml --repo QwenLM/qwen-code --ref fix/packaging-check-permissionsA workflow_dispatch run uses the workflow file from the selected ref, so the load-time permission comparison would be exercised against exactly this change and a startup_failure vs. a real job list would settle it before merge.
I did not run it, and I would not. It fans out into the release workflow's whole build matrix on shared runners to save at most a day of latency, and cron: '0 16 * * *' confirms it for free within a day of merge. Worth correcting the sentence so the option is not recorded as impossible — the honest scope note is "not verified because the matrix is not worth spending on a permission fix the daily schedule confirms anyway", which is a different claim from "cannot be verified".
On the "narrower alternative" paragraph
The description rejects narrowing the callee's publish declaration, and I agree — that trades a real safeguard for a cosmetic one. There is a third option it does not name: extract prepare + build into a workflow_call-only workflow that both desktop-release.yml and this check call, so the check would grant contents: 'read' and never reference the publish path at all.
I would still not do it, and the reason is this workflow's own motivation: it exists because a CLI change broke AppImage bundling and only a real release run caught it, two weeks late. Calling a build-only sub-workflow would stop exercising desktop-release.yml itself — the wiring, the gates, the artifact hand-off — which is a large part of what makes the check worth running daily. Calling the real workflow and granting what it declares is the right trade. Naming it here only so that paragraph is not read as if one alternative had been considered and no others existed.
Merge mechanics
desktop-packaging-check.yml has no .github/CODEOWNERS entry (the only workflow paths owned are release.yml, finalize-release.yml and security-checks.yml), so no code-owner approval is required — two approvals from anyone with write access satisfy main's ruleset. CI at 8f6f0519: everything green except Test (ubuntu-latest, Node 22.x) and review-pr, both in flight. Head is a merge of main at 2488d12d, so the diff against base is the one file and nothing else.
中文说明
在 head 8f6f0519 上的自审(GitHub 不允许我批准自己的 PR)。没有阻塞项。 这次授权是正确、最小、且在所有可达路径上可证明为惰性的。下面两条说明,其中一条更正了正文里的一个说法。
对照 callee 核实,而不是对照描述
我把 desktop-release.yml 里每一个 job 级 permissions: 块都列了出来,而不是采信摘要:prepare(:82) 与 build(:153) 都没有声明、继承 callee 顶层的 contents: 'read',二者可达;publish(:593) 声明 contents: 'write'(:601-602),不可达——门禁是 if: inputs.dry_run == false && …,而调用方硬编码 dry_run: true;sync-oss(:739) 声明 actions: 'read' + contents: 'read'(:746-748),不可达,见下。
所以调用方必须覆盖的并集是 contents: write + actions: read + contents: read,而新增的块恰好授予 actions: 'read' + contents: 'write'——完整,且 write 已包含 read。没有任何遗漏,所以那次让运行起不来的加载期比较现在应当通过。
sync-oss 值得单独说明,因为它正是 actions: 'read' 那一半的来源,而它被四重独立排除:门禁是 github.event_name == 'workflow_dispatch' && inputs.dry_run == false && inputs.draft == false && inputs.prerelease == false && github.repository == 'QwenLM/qwen-code',而调用方传的是 dry_run: true、draft: true、prerelease: true——并且 schedule 触发时 event_name 本来就不是 workflow_dispatch。它自身还是一个嵌套的 reusable 调用(uses: './.github/workflows/sync-desktop-to-oss.yml'),这正是它声明的权限会向上传导到本调用方的原因。
新注释没提到、但应该提到的一层纵深防御。 因为 callee 的顶层 permissions 是 contents: 'read',所以本检查真正会跑的 prepare 与 build 继承的是只读,即使它们自己的门禁将来变了也无法消费这个 write 授权。write 只能被 publish 消费,而它在此不可达。所以该授权是双重惰性的:条件上不可达,且没有任何会运行的 job 持有它。将来某个改 dry_run 或给调用方加 job 的人,应该能只从这个文件里看出这一点;现在他只能看出 dry_run 那一半。
诊断本身我也确认了而不是假定:run 34444598159 的 conclusion 是 startup_failure,created_at 06:18:05Z → updated_at 06:18:07Z,run_attempt: 1,jobs 列表为空——正是正文所说的 2 秒、零 job、零 annotation 形态。
正文高估了「合并前无法验证」
Workflow permission resolution only happens on GitHub's side, so this can only be confirmed by dispatching after merge.
这句说过头了。本分支在仓库内(QwenLM:fix/packaging-check-permissions,cross_repo: false),且该 ref 上的文件 blob 是 cf2c37a47b——与 diff 的后像一致——所以 workflow_dispatch 接受它:gh workflow run desktop-packaging-check.yml --repo QwenLM/qwen-code --ref fix/packaging-check-permissions。workflow_dispatch 用所选 ref 上的 workflow 文件,因此加载期权限比较会正好作用在这次改动上,startup_failure 与真实 job 列表之别在合并前就能定论。
我没有跑它,也不建议跑。 它会在共享 runner 上扇出整个 release build matrix,只为了省下至多一天的延迟,而 cron: '0 16 * * *' 会在合并后一天内免费确认。值得更正这句话,免得这个选项被记录成不可能——诚实的范围说明是「未验证,因为为一个由每日定时任务即可确认的权限修复花掉整个 matrix 不值得」,这与「无法验证」是不同的主张。
关于「更窄的替代方案」那一段
正文否决了收窄 callee 的 publish 声明,我同意——那是拿一个真实的保护去换一个调用方表面上的好看。还有一个它没点名的选项:把 prepare + build 抽成一个只带 workflow_call 的 workflow,让 desktop-release.yml 与本检查都去调它,这样检查只需授予 contents: 'read',完全不引用 publish 路径。
我仍然不会这么做,理由就是本 workflow 自己的动机:它存在是因为一次 CLI 改动搞坏了 AppImage 打包,而只有真实的 release 运行才发现,晚了两周。改调一个只含构建的子 workflow,就不再锻炼 desktop-release.yml 本身——它的接线、门禁、artifact 交接——而那正是这个检查值得每天跑的重要部分。调用真实的 workflow 并授予它声明的权限,是正确的取舍。之所以在这里点名,只是为了让那一段不被读成「只考虑过一个替代方案、且不存在其它选项」。
合并机制
desktop-packaging-check.yml 在 .github/CODEOWNERS 里没有条目(唯一被 own 的 workflow 路径是 release.yml、finalize-release.yml、security-checks.yml),所以不需要 code-owner 批准——两个有 write 权限的人批准即满足 main 的 ruleset。8f6f0519 上的 CI:除 Test (ubuntu-latest, Node 22.x) 与 review-pr 在跑之外全绿。head 是对 main(2488d12d)的合并,所以对 base 的 diff 只有这一个文件。
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Approved at head 8f6f0519.
Required CI is green — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke and both Desktop Shell lanes all completed successfully; only review-pr is still running.
The premise is real and not inferred: run 34444598159 is a workflow_dispatch of the packaging check that GitHub reports as startup_failure with no jobs, which is exactly the shape a caller/callee permission mismatch produces.
I checked that the grant is the required union rather than a guess, including the hop the description does not mention. The called workflow declares contents: 'read' at top level (desktop-release.yml:69), the publish job declares contents: 'write' (:601), and the mirror job declares actions: 'read' plus contents: 'read' (:746) — the caller's new actions: read + contents: write is precisely the superset of those three. It is also enough transitively: sync-oss is itself a uses: call into sync-desktop-to-oss.yml, whose job declares exactly actions: read, contents: read, so nothing deeper in the tree can re-trigger the same load-time rejection.
"Nothing here writes" is structural rather than a promise: publish is gated on inputs.dry_run == false && github.repository == 'QwenLM/qwen-code', the mirror job additionally on github.event_name == 'workflow_dispatch' — which is never true for a called workflow — and the caller hardcodes dry_run: true. The two jobs that actually run, prepare and build, declare no permissions of their own and therefore inherit the called workflow's top-level contents: read. So the widened caller entry is a ceiling that GitHub compares at load time, not an effective write grant to any job that executes in this check — which is why the narrower alternative the description declines (stripping the publish job's own declaration) really would trade a real safeguard for a cosmetic one.
The second commit is the right hygiene too: it merges main to pick up the web-shell suite realignment, which is what the currently-red Test lane on other branches needs, and it is why this PR's own suite is green rather than carrying an unrelated failure.
No new Critical found. Two non-blocking notes, the first already recorded on the PR:
- The contract that bit this check is still unpinned anywhere in the repo (
X1-1). The next job-level permission added todesktop-release.ymlwill fail the same way — silently, at load time, before anyifevaluates and with no annotation on the run. The.github/scriptsbattery thatLint & Staticalready runs parses these workflows, so a small assertion that each caller'spermissionscovers the union declared by its callee's jobs would turn a dead schedule into a red test. - The check's first real run is still ahead of this change: after merge, dispatch it once against
mainand confirm it fans out into the four build jobs with publish and the OSS mirror reporting skipped rather than running. That is also the acceptance criterion the description names.
|
Released in v0.23.3. |
What this PR does
Grants the scheduled desktop packaging check the two permissions the release workflow's own jobs declare, so it can start.
Why it's needed
The first dispatch of the check died in two seconds with no job, no step and no annotation — the shape of a startup failure. A workflow that calls another must grant every permission the called workflow's jobs declare, and the release workflow's publish job declares write access to contents while the OSS mirror job asks to read actions. The check granted read access to contents and nothing else.
The trap is that this validation happens when the file is loaded, before any condition is evaluated. Both of those jobs are unreachable from this check — it hardcodes a dry run, and each of them is additionally gated on a real publish — but being unreachable does not exempt their declared permissions from the comparison. So a check that can never write still has to say it may.
Nothing here writes. The dry run leaves the publish and mirror jobs skipped on their own conditions, exactly as it did in the release runs this reuses.
Reviewer Test Plan
How to verify
Dispatch the check against
mainand confirm it now starts and fans out into the release workflow's four build jobs, and that the publish and mirror jobs report skipped rather than running. Before this change the same dispatch produced a run with zero jobs.Evidence (Before & After)
Before: run 34444598159,
startup_failure, 2 seconds, no jobs. After: a run that reaches the build matrix.Tested on
Workflow permission resolution only happens on GitHub's side, so this can only be confirmed by dispatching after merge.
Environment (optional)
N/A
Risk & Scope
Linked Issues
Follows #11519.
中文说明
这个 PR 做了什么
为定时的桌面打包检查授予被调用 workflow 各 job 所声明的两项权限,使其能够正常启动。
为什么需要
该检查的第一次触发在两秒内结束,没有 job、没有 step、也没有任何注解——这是启动失败的典型形态。调用方 workflow 必须授予被调用 workflow 中各 job 所声明的全部权限,而发布 workflow 的 publish job 声明了对 contents 的写权限,OSS 镜像 job 则请求读取 actions。该检查只授予了 contents 的读权限。
陷阱在于:这项校验发生在文件被加载时,早于任何条件求值。这两个 job 从本检查出发都是不可达的——它把 dry run 写死为 true,而两者各自还有以真实发布为前提的门槛——但"不可达"并不能让它们声明的权限免于比对。因此一个永远不会写入的检查,仍然必须声明它可能写入。
这里不会有任何写入。dry run 会让 publish 与镜像 job 按各自条件保持 skipped,与它所复用的那些发布运行中的表现完全一致。
评审验证计划
如何验证
针对
main触发该检查,确认它现在能够启动并展开为发布 workflow 的四个构建 job,且 publish 与镜像 job 报告 skipped 而非实际运行。在此改动之前,同样的触发产生的是一个零 job 的运行。证据(Before & After)
之前:运行 34444598159,
startup_failure,2 秒,无任何 job。之后:能够走到构建矩阵的运行。测试平台
workflow 的权限解析只发生在 GitHub 侧,因此只能在合并后通过触发来确认。
环境(可选)
N/A
风险与范围
关联 Issue
承接 #11519。