Skip to content

ci(verify): provision a Java toolchain for sdk-java PRs - #13763

Merged
doudouOUC merged 2 commits into
mainfrom
feat/verify-java-toolchain
Oct 9, 2026
Merged

doudouOUC merged 2 commits into
mainfrom
feat/verify-java-toolchain

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

The sandboxed verify lane now prepares a Java toolchain before the agent's clock starts, and only when the pull request diff touches packages/sdk-java. The agent gets Temurin JDK 21, Maven 3.9.11, a restore-only Maven repository, and qwencode plus runtime-broker installed from the merge ref. Other pull requests download nothing.

The repository is filled by a trusted producer on main and restored read-only. This repo's own jars are removed before the cache is saved, because the module versions are fixed and a jar built from main would otherwise be what Maven resolves. The skill's environment contract tells the agent what the lane provides, including that an A/B must reinstall the sibling modules for the base side, and that database-backed integration tests stay not covered.

Why it's needed

Whether a Java pull request was actually executed in this lane depended on whether that round's agent spent its own time budget downloading a JDK. Some rounds did, some did not, and a later round could silently stop executing Java that an earlier round had executed. sdk-java.yml already tests Java on pull requests. The gap is the verify agent's own A/B verdict.

Reviewer Test Plan

How to verify

Confirm a diff outside packages/sdk-java does not download a JDK or restore the Maven repository. Confirm a Java diff reaches the agent with QWEN_VERIFY_JAVA=1, JAVA_HOME, and MAVEN_ARGS set, or, when the download fails, without those variables and with a warning, while the agent still runs. Confirm the verify job never saves the Maven cache.

The cache hit itself can only be observed after this merges and the producer has run on main: comment @qwen-code /verify twice on the same Java pull request. The second step summary should contain verify Maven repo: hit=true, the report should execute the Java side, and the agent should not download a JDK.

Unit coverage: npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-triage-workflow.test.js -t "qwen-triage verify Java toolchain|strips GitHub command files" and node --test .github/scripts/qwen-triage-workflow.test.mjs for the timeout floor (timeout-minutes >= 210). The node:test file also has pre-existing macOS failures in the flakiness-gate shell fixtures (/usr/bin/rm missing under that harness); the timeout assertion itself passed.

Evidence (Before & After)

N/A

Tested on

OS Status
🍏 macOS ✅
🊟 Windows N/A
🐧 Linux ✅

Environment (optional)

macOS: the workflow unit tests above. Linux: Lima node:22-bookworm (aarch64). The pinned x64 Temurin archive checksum matched the published file. On that arm64 guest the x64 binary cannot execute, and the script correctly leaves no ready marker. The same Temurin release's aarch64 build, after JAVA_HOME is exported, writes the marker: Java 21.0.12.1, Maven 3.9.11. Sibling installs of qwencode and runtime-broker succeeded as the node user. mvn test with only the agent environment: qwencode 208 tests, 0 failures. runtime-broker 754 tests with 1 failure and 1 error. managed-agent-server 1185 tests with 1 error. Those two modules include tests that start a Node fixture or a built CLI, and this rehearsal did not run npm run build first. The verify lane builds the TypeScript app before the agent starts.

Risk & Scope

  • Main risk or tradeoff: a cold Java diff can spend up to about 20 minutes on the download and the two capped sibling installs. The job limit moves from 190 to 210 minutes so the agent can still ship a partial report. The Maven cache is restore-only; a miss or a cache outage degrades to a cold repository.
  • Not validated / out of scope: MariaDB/MySQL failsafe integration tests, the hosted harness, the Java 11/17 matrix, and re-running Java tests through the flakiness gate. A cache hit on a second /verify of the same PR cannot be shown until the producer has saved on main.
  • Breaking changes / migration notes: none. verify-pr: add follow-up-round, publish-guard, mutation and rig-hygiene rules from maintainer verification rounds #13732 rule 11 should treat Java as not covered only when QWEN_VERIFY_JAVA is unset.

Design: docs/design/verify-java-toolchain.md and docs/design/verify-java-toolchain.zh-CN.md.

Linked Issues

Closes #13741

äļ­æ–‡čŊŽ

čŋ™äļŠ PR 做äŧ€äđˆ

æē™įŪą verify é€šé“įŽ°åœĻ侚åœĻäŧĢᐆčŪĄæ—ķ垀始äđ‹å‰å‡†å· Java å·Ĩ具é“ūåđķäļ”及åœĻ PR diff įǰ到 packages/sdk-java æ—ķ才做。äŧĢį†äžšæ‹ŋ到 Temurin JDK 21、Maven 3.9.11、及æĒåĪįš„ Maven äŧ“嚓äŧĨ及按合åđķåž•į”ĻčĢ…åĨ―įš„ qwencode 和 runtime-broker。å…ķäŧ– PR äļäžšäļ‹č――čŋ™äš›äļœčĨŋ。

äŧ“åš“į”ą main äļŠįš„åŊäŋĄį”Ÿäš§č€…åĄŦåĨ―æķˆčīđäū§åŠæĒåĪã€‚äŋå­˜å‰äžšåˆ æŽ‰æœŽäŧ“åš“č‡Šå·ąįš„ jar因äļšæĻĄå—į‰ˆæœŽæ˜Ŋ囚åŪšįš„ïžŒåĶ则 Maven 侚č§Ģ析到 main äž„åŧšå‡šæĨįš„é‚Ģäļ€äŧ―ã€‚æŠ€čƒ―įš„įŽŊåǃįšĶåŪšå†™æ˜Žé€šé“æäū›äŧ€äđˆïžŒåŒ…拎 A/B æ—ķåŋ…éĄŧäļš base äļ€äū§é‡čĢ…åŒįš§æĻĄå—äŧĨ及äūčĩ–æ•°æŪåš“įš„é›†æˆæĩ‹čŊ•äŧį„ķįŪ—æœŠčĶ†į›–ã€‚

äļšäŧ€äđˆéœ€č́

äĄ Java PR åœĻčŋ™äļŠé€šé“里有æēĄæœ‰čĒŦᜟæ­Ģæ‰§čĄŒïžŒå–å†ģ䚎é‚Ģäļ€č―ŪäŧĢᐆč‚Ŋäļč‚ŊčŠąč‡Šå·ąįš„æ—ķé—īåŽŧäļ‹č―― JDKã€‚æœ‰įš„č―ŪæŽĄäžšäļ‹č――ïžŒæœ‰įš„äļäžšïžŒåŽäļ€č―Ūčŋ˜åŊčƒ―æ‚„æ‚„åœæŽ‰å‰äļ€č―Ūå·ēįŧæ‰§čĄŒčŋ‡įš„ Java。sdk-java.yml å·ēįŧåœĻ PR äĩ‹čŊ• Javaã€‚įžšåĢæ˜Ŋ verify äŧĢį†č‡Šå·ąįš„ A/B įŧ“čŪšã€‚

åŪĄé˜…č€…æ€ŽäđˆéЌčŊ

įĄŪčŪĪ packages/sdk-java äŧĨåĪ–įš„ diff äļäžšäļ‹č―― JDKäđŸäļäžšæĒåĪ Maven äŧ“嚓。įĄŪčŪĪ Java diff 到čūūäŧĢᐆæ—ķå·ēčŪūį―Ū QWEN_VERIFY_JAVA=1、JAVA_HOME 和 MAVEN_ARGSïž›äļ‹č――åĪąčīĨæ—ķ则æēĄæœ‰čŋ™äš›å˜é‡ïžŒåđķæœ‰ warning同æ—ķäŧĢᐆäŧį„ķäžšč·‘ã€‚įĄŪčŪĪ verify äŧŧåŠĄäŧŽäļäŋå­˜ Maven įž“å­˜ã€‚

įž“å­˜å‘―äļ­åŠčƒ―åœĻ合åđķäđ‹åŽã€į”Ÿäš§č€…å·ēåœĻ main äļŠč·‘čŋ‡æ‰čƒ―įœ‹åˆ°ïžšåŊđ同äļ€äļŠ Java PR čŋžįŧ­äļĪæŽĄčŊ„čŪš @qwen-code /verifyã€‚įŽŽäšŒæŽĄįš„æ­ĨéŠĪ摘čĶåš”åŒ…åŦ verify Maven repo: hit=trueæŠĨå‘Šåš”æ‰§čĄŒ Java äū§ïžŒäŧĢᐆäļåš”再äļ‹č―― JDK。

单æĩ‹ïžšnpx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-triage-workflow.test.js -t "qwen-triage verify Java toolchain|strips GitHub command files"äŧĨ及 node --test .github/scripts/qwen-triage-workflow.test.mjs é‡Œįš„čķ…æ—ķäļ‹é™ïžˆtimeout-minutes >= 210。čŋ™äļŠ node:test 文äŧķåœĻ macOS äļŠčŋ˜æœ‰æ—Ēæœ‰įš„æŠ–åŠĻé—Ļ shell åĪđ具åĪąčīĨčŊĨįŽŊåĒƒé‡Œæ‰ūäļåˆ° /usr/bin/rmčķ…æ—ķ断čĻ€æœŽčšŦæ˜Ŋ通čŋ‡įš„。

čŊæŪ

äļé€‚į”Ļ。

æĩ‹čŋ‡įš„įŽŊåǃ

macOS äļŠč·‘䚆äļŠéĒįš„å·Ĩä―œæĩå•æĩ‹ã€‚Linux äļŠį”Ļ Lima įš„ node:22-bookwormaarch64。钉æ­ŧįš„ x64 Temurin å―’æĄĢæ ĄéŠŒå’ŒäļŽå‘å–‡äŧķäļ€č‡ī。åœĻčŋ™å° arm64 åŪĒæˆ·æœšäļŠ x64 乌čŋ›åˆķ无æģ•æ‰§čĄŒïžŒč„šæœŽæ­ĢįĄŪ地äļå†™ ready。同äļ€ Temurin į‰ˆæœŽįš„ aarch64 构åŧšåœĻåŊžå‡š JAVA_HOME äđ‹åŽäžšå†™ readyïžšJava 21.0.12.1Maven 3.9.11。äŧĨ node į”Ļ户åŪ‰čĢ… qwencode 和 runtime-broker éƒ―æˆåŠŸã€‚åŠį”ĻäŧĢᐆįŽŊåĒƒč·‘ mvn testïžšqwencode 208 äļŠæĩ‹čŊ•、0 åĪąčīĨ。runtime-broker 754 äļŠæĩ‹čŊ•里有 1 äļŠåĪąčīĨ和 1 äļŠé”™čŊŊ。managed-agent-server 1185 äļŠæĩ‹čŊ•里有 1 äļŠé”™čŊŊ。后äļĪäļŠæĻĄå—åŒ…åŦåŊåŠĻ Node fixture 或å·ē构åŧš CLI įš„æĩ‹čŊ•čŋ™æŽĄæž”įŧƒæēĄæœ‰å…ˆč·‘ npm run build。verify 通道åœĻäŧĢį†åž€å§‹å‰äžšå…ˆæž„åŧš TypeScript åš”į”Ļ。

éĢŽé™ĐäļŽčŒƒå›ī

  • äļŧčĶå–čˆïžšå†·įš„ Java diff 最åĪščŠąåΧįšĶ 20 分钟äļ‹č――åđķ做äļĪæŽĄå°éĄķįš„åŒįš§æĻĄå—åŪ‰čĢ…ã€‚äŧŧåŠĄäļŠé™äŧŽ 190 分钟æ”đäļš 210 分钟äŧĢᐆäŧį„ķčƒ―äšĪ回éƒĻ分æŠĨ告。Maven įž“å­˜åŠæĒåĪïž›æœŠå‘―äļ­æˆ–įž“å­˜æœåŠĄæ•…éšœäžšé€€åŒ–äļšå†·äŧ“嚓。
  • 朊銌čŊ / äļåœĻ范å›ī内MariaDB/MySQL įš„ failsafe 集成æĩ‹čŊ•、hosted harness、Java 11/17 įŸĐé˜ĩäŧĨ及į”Ļ抖åŠĻé—Ļé‡č·‘ Java æĩ‹čŊ•。同äļ€ PR įŽŽäšŒæŽĄ /verify įš„įž“å­˜å‘―äļ­ïžŒčĶį­‰į”Ÿäš§č€…åœĻ main äļŠäŋå­˜äđ‹åŽæ‰čƒ―įœ‹åˆ°ã€‚
  • į ī坏性变æ›ī / čŋį§ŧ无。verify-pr: add follow-up-round, publish-guard, mutation and rig-hygiene rules from maintainer verification rounds #13732 įŽŽ 11 æĄåš”åŠåœĻ QWEN_VERIFY_JAVA äļå­˜åœĻæ—ķ把 Java 视äļšæœŠčĶ†į›–ã€‚

čŪūčŪĄæ–‡æĄĢïžšdocs/design/verify-java-toolchain.md äļŽ docs/design/verify-java-toolchain.zh-CN.md。

å…ģ联 Issue

Closes #13741

The verify lane could not execute Java unless the agent spent its own time downloading a JDK. Install JDK 21 and Maven before the agent starts, and restore a Maven repository that does not contain this repo's own jars, only when the diff touches packages/sdk-java.
@qwen-code-review-bot

qwen-code-review-bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Qwen Triage finished — CI landed green on 1d81dfa and the deferred approval was posted. finalize run

✅ Qwen Triage å·ēåŪŒæˆ —— 1d81dfa įš„ CI å…ĻįŧŋåŧķčŋŸåŪĄæ‰đå·ē提äšĪ。æŸĨįœ‹ finalize čŋčĄŒ

@doudouOUC
doudouOUC requested review from wenshao and yiliang114 and removed request for wenshao October 9, 2026 10:53
@doudouOUC doudouOUC self-assigned this Oct 9, 2026
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — every heading present, Risk & Scope filled in honestly (the "cannot be shown until the producer has run on main" line is exactly the right thing to volunteer), and the design doc ships in both languages with reciprocal links and a 1:1 heading structure.

Problem: observed, not theoretical. #13741 names five specific verify rounds (#13335, #13554, #13550, #13572, #12754) with links to the reports, including one where all 18 changed Java files went unexecuted — a regression against the previous round on the same PR. That is documented, recurring nondeterminism in a lane maintainers use for A/B verdicts, not a hypothesis.

Direction: aligned. This is the repo's own CI tooling, the issue was filed by a maintainer, and the shape follows an existing precedent instead of inventing one — pnpm-store.yml already establishes trusted-producer-on-main plus a restore-only consumer on the same runner, image, and actions/cache SHA pin. The new producer copies that contract rather than adding a second mechanism. Saying it plainly because it matters: this does touch the CI trust boundary, since it pulls external binaries into the container that later executes PR code. I read that part closely rather than waving it through — the findings are in Stage 2.

Size: no core paths (packages/core/src/**, packages/*/src/{auth,providers,models,config,tools,services}/**), so the two-tier core gate does not apply. Of +971/−17 across 8 files: ~436 lines are the two design docs, ~254 are workflow tests, ~298 are production (140 lines in qwen-triage.yml, the 124-line producer, 28 lines of skill contract, 3 of size baseline). Under the 1000-line advisory.

Approach: the scope feels right and I could not find anything worth cutting. The obvious smaller version — an unconditional actions/setup-java — makes every non-Java verify run pay for a JDK download, which is the cost #13741 explicitly asks to avoid; and leaving ~/.m2 to the agent is the thing being fixed. The path gate, the restore-only cache, and the artifact stripping are each load-bearing rather than decorative. No drive-by changes: outside the feature the only edits are the .size-baseline ratchet bump and one skill paragraph about Android that this change made factually wrong ("ships no JDK").

Risk: no elevated risk signals — none of the changed files match the revert-correlated path list.

Moving on to code review. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪ

æĻĄæŋåیæ•ī ✓ —— 各标éĒ˜é―å…ĻRisk & Scope 写åū—åūˆåŪžåœĻäļŧåŠĻčŊŽã€Œįž“å­˜å‘―äļ­čĶį­‰į”Ÿäš§č€…åœĻ main äļŠč·‘čŋ‡æ‰čƒ―įœ‹åˆ°ã€ïžŒčŋ™æ­Ģæ˜Ŋåš”čŊĨčŪēæļ…æĨšįš„éƒĻ分čŪūčŪĄæ–‡æĄĢ双čŊ­é―åĪ‡ã€äš’į›ļé“ūæŽĨ、标éǘįŧ“æž„äļ€äļ€åŊđ嚔。

é—ŪéĒ˜ïžš æ˜Ŋå·ē观æĩ‹åˆ°įš„äļæ˜ŊᐆčŪšæ€§įš„ã€‚#13741 į‚đ名䚆䚔č―Ūå…·ä―“įš„ verify#13335、#13554、#13550、#13572、#12754åđķ附乆æŠĨ告é“ūæŽĨå…ķäļ­äļ€č―Ū 18 äļŠæ”đåŠĻįš„ Java 文äŧķåیå…ĻæēĄæ‰§čĄŒâ€”—į›ļåŊđ同äļ€ PR įš„äļŠäļ€č―Ūæ˜Ŋ倒退。čŋ™æ˜ŊįŧĪ者į”ĻæĨ做 A/B įŧ“čŪšįš„é€šé“é‡Œå·ēčĒŦčŪ°å―•ã€ååĪå‡šįŽ°įš„äļįĄŪåŪšæ€§ïžŒäļæ˜Ŋ假čŪū。

æ–đ向 åŊđé―ã€‚čŋ™æ˜Ŋäŧ“åš“č‡Šå·ąįš„ CI å·Ĩ具issue į”ąįŧĪč€…æå‡šïžŒč€Œäļ”做æģ•æēŋį”Ļ乆å·ēæœ‰įš„å…ˆäū‹č€Œäļæ˜ŊåĶ造äļ€åĨ———pnpm-store.yml å·ēįŧįĄŪįŦ‹äš†ã€Œmain äļŠįš„åŊäŋĄį”Ÿäš§č€… + 及æĒåĪįš„æķˆčīđäū§ã€æĻĄåžïžŒį”ĻåŒæ ·įš„ runner、镜像和 actions/cache SHA é’‰į‰ˆã€‚æ–°įš„į”Ÿäš§č€…į…§æŽčŋ™åĨ—įšĶåŪšïžŒæēĄæœ‰åž•å…ĨįŽŽäšŒį§æœšåˆķ。有äļ€į‚đč́čŪ翘ŽïžŒå› äļšåۃįĄŪåŪžé‡čĶïžščŋ™äļŠæ”đåŠĻįĒ°åˆ°äš† CI įš„äŋĄäŧŧčūđį•ŒïžŒåŪƒäžšæŠŠåĪ–éƒĻ乌čŋ›åˆķ拉čŋ›é‚ĢäļŠäđ‹åŽčĶæ‰§čĄŒ PR äŧĢį įš„åŪđå™Ļ。čŋ™éƒĻ分我æ˜Ŋäŧ”įŧ†čŊŧčŋ‡įš„äļæ˜Ŋæ”ūčŋ‡â€”—įŧ“čŪšåœĻ Stage 2。

规æĻĄïžš æēĄæœ‰č§Ķ及æ ļåŋƒč·Ŋåū„packages/core/src/**、packages/*/src/{auth,providers,models,config,tools,services}/**所äŧĨäļĪįš§æ ļåŋƒé—Ļ槛äļé€‚į”Ļ。8 äļŠæ–‡äŧķ +971/−17 äļ­ïžšįšĶ 436 行æ˜ŊäļĪäŧ―čŪūčŪĄæ–‡æĄĢįšĶ 254 行æ˜Ŋå·Ĩä―œæĩæĩ‹čŊ•įšĶ 298 行æ˜Ŋį”Ÿäš§äŧĢį ïžˆqwen-triage.yml 140 čĄŒã€124 čĄŒįš„į”Ÿäš§č€…ã€28 čĄŒæŠ€čƒ―įšĶåŪšã€3 čĄŒä―“į§Ŋ埚įšŋïž‰ã€‚ä―ŽäšŽ 1000 čĄŒįš„åΧ PR 提įĪšįšŋ。

æ–đæĄˆïžš 范å›īåˆį†ïžŒæˆ‘æēĄæ‰ū到åŊäŧĨį æŽ‰įš„éƒĻ分。最į›īč§‚įš„æ›īå°į‰ˆæœŽâ€”â€”æ— æĄäŧķį”Ļ actions/setup-java——侚čŪĐæŊäļŠéž Java įš„ verify éƒ―äļšäļ‹č―― JDK äŧ˜å‡šäŧĢäŧ·ïžŒč€Œčŋ™æ­Ģæ˜Ŋ #13741 明įĄŪčĶæą‚éŋå…įš„æˆæœŽïž›æŠŠ ~/.m2 äšĪįŧ™äŧĢį†č‡Šå·ąåŽŧåĄŦ则æ­Ģæ˜Ŋčŋ™æŽĄč́äŋŪįš„é—ŪéĒ˜æœŽčšŦ。č·Ŋåū„é—Ļ控、及æĒåĪįš„įž“å­˜ã€äŧĨ及å‰ĨįĶŧ朎äŧ“嚓䚧į‰Đäļ‰č€…éƒ―æ˜Ŋæ‰ŋ重įŧ“æž„č€Œäļæ˜ŊčĢ…éĨ°ã€‚æēĄæœ‰åĪđåļĶæ— å…ģæ”đåŠĻïžšį‰đ性äđ‹åĪ–åŠæœ‰ .size-baseline įš„æĢ˜č―Ūæ›ī新äŧĨåŠæŠ€čƒ―é‡Œé‚ĢæŪĩčĒŦ朎æ”đåŠĻæ”đ写äļšäš‹åŪžé”™čŊŊįš„ Android čŊŽïžˆã€Œships no JDK」。

éĢŽé™Đïžš æ— å‡įš§éĢŽé™ĐäŋĄå·â€”—æ”đåŠĻ文äŧķéƒ―æēĄæœ‰å‘―äļ­äļŽ revert į›ļå…ģįš„č·Ŋåū„列čĄĻ。

čŋ›å…ĨäŧĢ᠁åŪĄæŸĨ 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1d81dfa7567e789a4b9045a6d0029b7eb528cd5d · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Code review

I wrote down what I would have done before reading the diff: gate on the changed-file list, download a checksum-pinned Temurin + Maven before the agent's clock starts, warm ~/.m2 from a trusted producer on main that the PR lane only restores, strip this repo's own jars so a stale main build can't win dependency resolution, export a flag the skill can key on, raise the job timeout, and extend the workflow tests. The PR does all of that, and then adds four things I had not thought of — each of which closes a hole my version would have shipped with:

  • the toolchain installs before checkout, so no PR tree is on disk while it runs, and /opt/verify-java is root-owned with a+rX, so the node user cannot plant the ready marker that gates the agent's environment;
  • ready is written only after both checksums and java -version / mvn -version succeed, with JAVA_HOME exported first because the Maven launcher will not find the unpacked JDK on its own;
  • the node-run mvn strips ACTIONS_RUNTIME_TOKEN / ACTIONS_CACHE_URL alongside the GitHub command files, so a plugin declared in a PR-owned POM cannot write to the shared cache — and actions/cache/save is asserted absent from the whole verify job;
  • the strip list is not hardcoded prose: moduleCoordinates() reads every packages/sdk-java/*/pom.xml and asserts the list equals the set of artifact ids, so adding a fifth module without updating the producer fails a test.

No critical blockers. Correctness, the supply-chain surface, and the failure paths all hold up. Specifics I verified rather than assumed:

  • MAVEN_VERSION / MAVEN_SHA512 match sdk-java.yml byte-for-byte, and the pins are asserted equal across consumer, producer, and sdk-java.yml so they cannot drift. packages/sdk-java/ has no parent POM, so hashFiles('packages/sdk-java/*/pom.xml') covers all four modules and the producer's key and the consumer's key are the same hash.
  • managed-agent-server test-depends on qwen-managed-runtime-broker with <type>test-jar</type>, and the broker's maven-jar-plugin does bind the test-jar goal — so -DskipTests (not -Dmaven.test.skip) still compiles tests and publishes the jar the server needs. The producer's install order (qwencode → runtime-broker → managed-agent-server → client) satisfies that. None of the four POMs use frontend-maven-plugin or exec-maven-plugin, so the producer needs no node_modules.
  • QWEN_ENV is consumed via runuser -u node -- env -i "${QWEN_ENV[@]}", and GNU env applies assignments left to right — so the appended PATH=/opt/verify-java/...:${PATH} really does override the earlier "PATH=$PATH". The comment claiming that is correct.
  • Install Java modules and Run verification agent share the identical gate (decision == 'run' && prepare.outputs.verdict == ''), so there is no state where the agent gets QWEN_VERIFY_JAVA=1 while java-prepare.log was never written.
  • .size-baseline is exact: the head blobs are 362377 and 5575 bytes, matching the recorded entries, so the ratchet neither trips nor banks slack.
  • The runuser count assertion moving 7 → 8 is right — the new step contributes exactly one single-line runuser -u node -- env match inside the sliced region.

Two non-blocking things I'd want you to consider, neither of which I think should hold the merge:

  1. Can the producer re-save over an existing key? The save key is content-derived with no run-unique component, but on.push.paths also includes .github/workflows/verify-maven-repo.yml itself and workflow_dispatch is enabled. My understanding is that actions/cache/save fails when the key already exists on the cache server, which would mean editing the producer without touching a POM turns the job red on main and — the part that actually matters — leaves the cache holding the previous contents. That is the opposite of the intent when the edit changed what belongs in the repo (a new populate line, an extended strip list). pnpm-store.yml cannot hit this because its only trigger path is the hashed file. I could not test it from here, so treat this as a question, not a finding: if the collision is real, evicting the entry before save (or dropping the workflow-file trigger and documenting a manual eviction for workflow_dispatch) would close it.
  2. A cold cache pushes the cost back into the agent budget. On a restore miss the repo is empty, and timeout -k 30 300 will kill both sibling installs mid-download, logging qwencode=124 runtime-broker=124. The skill contract then says "reinstall that module from the tree", which is a full dependency download charged against the agent's 120 minutes — the thing ci(verify): provision a Java toolchain in the verify lane so sdk-java PRs are verified every time #13741 set out to remove. The JDK and Maven are still guaranteed, so the regression is bounded, and the PR body does disclose the ~20-minute cold path. It might be worth a distinct line in the contract for a timeout specifically: 124 means the repository is cold, so budget for a full resolve rather than retrying the install.
Files changed (8)
File What changed
.github/workflows/qwen-triage.yml The whole consumer side: java output from the paginated file list, the root-run toolchain install before checkout, the stale-clear plus restore-only cache pair, the capped node-run sibling install, the ready-gated agent env, and the 190 → 210 timeout with its budget comment rewritten.
.github/workflows/verify-maven-repo.yml New producer. Trusted main-only context, same runner and image as the consumer, four modules resolved into a local repo, then this repo's own com/alibaba artifacts deleted before the save.
.qwen/skills/verify-pr/SKILL.md The environment contract the agent reads: what the lane provides, where the exit codes live, why the base side of an A/B needs a sibling reinstall, and what stays under Not covered. Also corrects the Android paragraph that claimed the image ships no JDK.
.github/workflows/.size-baseline Ratchet bump for the grown qwen-triage.yml and the new producer entry. Both verified exact against the head blobs.
docs/design/verify-java-toolchain.md English design doc: problem, current state, goals and non-goals, the step-ordering flowchart, decisions, constraints, risks, validation, acceptance criteria.
docs/design/verify-java-toolchain.zh-CN.md Chinese counterpart. Heading structure matches the English version one for one.
scripts/tests/qwen-triage-workflow.test.js New verify Java toolchain suite covering the gate, the pre-checkout ordering, checksum-before-ready, cross-file pin equality, the restore-only property, the POM-derived strip list, the node-run caps, the env injection order, and the timeout. Plus the 7 → 8 runuser count.
.github/scripts/qwen-triage-workflow.test.mjs The node:test timeout floor moves 190 → 210 with the budget comment updated to account for the Java steps.

Testing

This is an unattended CI run, so per the lane's rules I did not build or execute anything from this PR — the evidence below is the PR's own CI, read through the API for the reviewed commit. 61 check-runs in total: 10 reported a real conclusion, 50 were skipped (the platform and event matrices that this diff does not select), 1 is queued and 2 are still in progress, and none failed or were cancelled.

The two that matter most for this change — Lint & Static (ubuntu-latest, Node 22.x) and Test (ubuntu-latest, Node 22.x), the latter being where both workflow test suites run — had not finished when I fetched. I did not poll for them; the Qwen Triage Finalize job rewrites the table below in place once CI settles. triage in progress is this run.

Final CI results for 1d81dfa (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / æŊäļŠæĢ€æŸĨ名äļ€čĄŒïžˆå–最新äļ€æŽĄčŋčĄŒïž‰ïžŒįœį•Ĩ skippedåĪąčīĨéĄđ排åœĻ最前。

Because those two are unfinished, I am explicitly recording what this review does not carry: no local execution of either workflow test suite, and no observation of the producer or the provisioning path running. The author's rehearsal numbers in the PR body (Lima node:22-bookworm on aarch64, mvn test counts per module) are the author's claim about their own environment, not evidence I re-ran — and by their own account that rehearsal skipped npm run build, which the real lane does first.

Sandboxed verification would settle part of this, and it is worth being precise about which part: @qwen-code /verify (available directly — the author has write access, so this is not a sponsored run). This diff touches .github/, .qwen/, docs/, and scripts/tests/ but not packages/sdk-java/, so on this PR's own verify run steps.pr.outputs.java is false and none of the new steps execute. That run does prove the negative acceptance criterion — a non-Java diff downloads no JDK, restores no Maven repo, installs no siblings — and that the lane still completes end-to-end with the new steps present but inert. It cannot prove the positive path: QWEN_VERIFY_JAVA=1 reaching an agent, the sibling installs landing, or verify Maven repo: hit=true on a second run. Those need a Java-touching PR after merge, once the producer has saved on main. Not verified: the positive path, for exactly that reason — no pre-merge lane can exercise it, and I would rather say so than imply the CI table covers it. /tmux is not applicable; there is no TUI surface here.

äļ­æ–‡čŊŽ

äŧĢ᠁åŪĄæŸĨ

åœĻčŊŧ diff äđ‹å‰ïžŒæˆ‘先写äļ‹äš†č‡Šå·ąäžšæ€ŽäđˆåšïžšæŒ‰æ”đåŠĻ文äŧķ列čĄĻé—Ļ控、åœĻäŧĢᐆčŪĄæ—ķ垀始前äļ‹č――é’‰į‰ˆåđķæ ĄéŠŒįš„ Temurin äļŽ Mavenã€į”ą main äļŠįš„åŊäŋĄį”Ÿäš§č€…éĒ„įƒ­ ~/.m2 而 PR 通道及æĒåĪã€å‰ĨįĶŧ朎äŧ“åš“č‡Šå·ąįš„ jar äŧĨ免 main įš„æ—§æž„åŧšåœĻäūčĩ–č§Ģ析äļ­čƒœå‡šã€åŊžå‡šäļ€äļŠæŠ€čƒ―åŊäŧĨæŪæ­ĪåˆĪæ–­įš„åž€å…ģ、抎éŦ˜äŧŧåŠĄčķ…æ—ķ、åđķæ‰Đ充å·Ĩä―œæĩæĩ‹čŊ•。čŋ™äļŠ PR å…Ļéƒ―åšäš†ïžŒč€Œäļ”čŋ˜čĄĨ䚆四į‚đ我æēĄæƒģåˆ°ã€č€Œæˆ‘įš„į‰ˆæœŽäžšåļĶį€æžæīžäļŠįšŋįš„äļœčĨŋïžš

  • å·Ĩ具é“ūåœĻ checkout äđ‹å‰åŪ‰čĢ…ïžŒčŋčĄŒæ—ķįĢį›˜äļŠæēĄæœ‰ PR æ ‘ïž›/opt/verify-java åąž root、权限 a+rX所äŧĨ node į”Ļ户无æģ•䞊造é‚ĢäļŠå†ģåۚäŧĢᐆįŽŊåĒƒįš„ ready 标čŪ°ïž›
  • ready 及åœĻäļĪäļŠæ ĄéŠŒå’ŒäŧĨ及 java -version / mvn -version éƒ―æˆåŠŸäđ‹åŽæ‰å†™ïžŒåđķäļ”å…ˆåŊžå‡š JAVA_HOME——Maven åŊåŠĻč„šæœŽč‡Šå·ąæ‰ūäļåˆ°åˆšč§ĢåŒ…įš„ JDKïž›
  • äŧĨ node čšŦäŧ―č·‘įš„ mvn é™Ī乆 GitHub å‘―äŧĪæ–‡äŧķäđ‹åĪ–čŋ˜å‰ĨįĶŧ乆 ACTIONS_RUNTIME_TOKEN / ACTIONS_CACHE_URL所äŧĨ PR č‡Šæœ‰ POM 里åĢ°æ˜Žįš„æ’äŧķæ— æģ•å†™å…ąäšŦįž“å­˜ïž›åŒæ—ķæ•īäļŠ verify äŧŧåŠĄčĒŦ断čĻ€äļåŦ actions/cache/saveïž›
  • å‰ĨįĶŧ列čĄĻäļæ˜Ŋ写æ­ŧįš„æ–‡å­—ïžšmoduleCoordinates() 侚čŊŧ取 packages/sdk-java/*/pom.xml äŊäļŠæĻĄå—ïžŒæ–­čĻ€åˆ—čĄĻäļŽ artifact id 集合į›ļį­‰ïžŒå› æ­Ī新åĒžįŽŽäš”äļŠæĻĄå—č€ŒæēĄåŒæ­Ĩį”Ÿäš§č€…äžščŪĐæĩ‹čŊ•åĪąčīĨ。

æēĄæœ‰é˜ŧ断性é—ŪéĒ˜ã€‚ æ­ĢįĄŪ性、äū›åš”é“ūéĒ、åĪąčīĨč·Ŋåū„éƒ―įŦ™åū—ä―ã€‚äŧĨä˜Ŋ我åŪžé™…æ ļåŊđčŋ‡ã€č€Œäļæ˜Ŋæƒģå―“į„ķįš„į‚đïžš

  • MAVEN_VERSION / MAVEN_SHA512 äļŽ sdk-java.yml é€å­—čŠ‚äļ€č‡īåđķäœ‰æ–­čĻ€äŋčŊæķˆčīđäū§ã€į”Ÿäš§č€…、sdk-java.yml äļ‰åĪ„é’‰į‰ˆį›ļį­‰ïžŒäļäžšæž‚į§ŧ。packages/sdk-java/ æēĄæœ‰įˆķ POM所äŧĨ hashFiles('packages/sdk-java/*/pom.xml') č͆ᛖå…ĻéƒĻ四äļŠæĻĄå—ïžŒį”Ÿäš§č€…äļŽæķˆčīđäū§įš„ key æ˜Ŋ同äļ€äļŠå“ˆåļŒã€‚
  • managed-agent-server äŧĨ <type>test-jar</type> æĩ‹čŊ•äūčĩ– qwen-managed-runtime-brokerïžŒč€Œ broker įš„ maven-jar-plugin įĄŪåŪžįŧ‘åŪšäš† test-jar goal——所äŧĨį”Ļ -DskipTestsïžˆč€Œäļæ˜Ŋ -Dmaven.test.skipäŧäžšįž–čŊ‘æĩ‹čŊ•åđķ发åļƒ server 需čĶįš„é‚ĢäļŠ jarã€‚į”Ÿäš§č€…įš„åŪ‰čĢ…éĄšåšïžˆqwencode → runtime-broker → managed-agent-server → clientæŧĄčķģčŋ™äļ€į‚đ。四äļŠ POM éƒ―æēĄæœ‰į”Ļ frontend-maven-plugin 或 exec-maven-plugin因æ­Īį”Ÿäš§č€…äļéœ€č́ node_modules。
  • QWEN_ENV æ˜Ŋ通čŋ‡ runuser -u node -- env -i "${QWEN_ENV[@]}" æķˆčīđįš„ïžŒGNU env äŧŽå·Ķ到åģåš”į”Ļčĩ‹å€žâ€”—所äŧĨ后čŋ―åŠ įš„ PATH=/opt/verify-java/...:${PATH} įĄŪåŪžčĶ†į›–äš†å‰éĒįš„ "PATH=$PATH"。é‚ĢåĨæģĻ释æ˜ŊåŊđįš„ã€‚
  • Install Java modules äļŽ Run verification agent į”Ļįš„æ˜Ŋåیå…Ļį›ļåŒįš„é—ĻæŽ§æĄäŧķdecision == 'run' && prepare.outputs.verdict == ''所äŧĨäļå­˜åœĻäŧĢᐆæ‹ŋ到䚆 QWEN_VERIFY_JAVA=1ã€č€Œ java-prepare.log äŧŽæœŠčĒŦå†™å‡šįš„įŠķ态。
  • .size-baseline æ˜Ŋ准įĄŪįš„ïžšhead äļŠįš„ blob 分åˆŦæ˜Ŋ 362377 和 5575 å­—čŠ‚ïžŒäļŽčŪ°å―•å€žäļ€č‡īæĢ˜č―Ūæ—Ēäļäžšč§Ķ发äđŸäļäžšå›Īä―™čĢ•ã€‚
  • runuser čŪĄæ•°æ–­čĻ€äŧŽ 7 æ”đ成 8 æ˜ŊåŊđįš„â€”â€”æ–°æ­ĨéŠĪåœĻåˆ‡į‰‡åŒšåŸŸå†…æ°åĨ―čīĄįŒŪäĄå•čĄŒ runuser -u node -- env åŒđ配。

äļĪį‚đäļé˜ŧæ–­ã€ä―†æˆ‘åœ›ä― č€ƒč™‘įš„äš‹ïžŒæˆ‘čŪĪäļšéƒ―äļčŊĨæ‹Ķä―åˆåđķïžš

  1. į”Ÿäš§č€…čƒ―åœĻå·ē存åœĻįš„ key äļŠé‡æ–°äŋå­˜å— äŋå­˜ key į”ąå†…åŪđæīūį”Ÿã€æēĄæœ‰čŋčĄŒå”Ŋäļ€åˆ†é‡ïžŒä―† on.push.paths 同æ—ķ包åŦ乆 .github/workflows/verify-maven-repo.yml 臩čšŦåđķäļ”垀䚆 workflow_dispatchã€‚æˆ‘įš„į†č§Ģæ˜Ŋ actions/cache/save åœĻ key å·ē存åœĻæ—ķ侚åĪąčīĨé‚Ģå°ąæ„å‘ģį€æ”đåŠĻį”Ÿäš§č€…č€ŒæēĄåŠĻ POM 侚čŪĐ main äļŠįš„äŧŧåŠĄå˜įšĒïžŒč€ŒįœŸæ­Ģč́įī§įš„æ˜Ŋâ€”â€”įž“å­˜é‡Œį•™įš„æ˜ŊäļŠäļ€æŽĄįš„å†…åŪđã€‚å―“čŋ™æŽĄæ”đåŠĻ朎意æ˜Ŋæ”đ变äŧ“嚓里čŊĨ有äŧ€äđˆïžˆæ–°åĒžäļ€čĄŒ populate、æ‰Đ充å‰ĨįĶŧ列čĄĻæ—ķčŋ™äļŽæ„å›ūæ­ĢåĨ―į›ļ反。pnpm-store.yml įǰäļåˆ°čŋ™äļŠé—ŪéĒ˜ïžŒå› äļšåۃå”Ŋäļ€įš„č§Ķ发č·Ŋåū„å°ąæ˜ŊčĒŦ哈åļŒįš„é‚ĢäļŠæ–‡äŧķ。我åœĻčŋ™é‡ŒæēĄæģ•åŪžæĩ‹ïžŒæ‰€äŧĨčŊ·æŠŠåŪƒå―“ä―œį–‘é—Ū而äļæ˜Ŋįŧ“čŪšïžšåĶ‚æžœå†ēįŠįĄŪåŪžå­˜åœĻäŋå­˜å‰å…ˆéб逐čŊĨæĄį›Ū或åŽŧ掉å·Ĩä―œæĩæ–‡äŧķčŋ™äļŠč§Ķ发č·Ŋåū„、把 workflow_dispatch įš„æ‰‹å·ĨéĐąé€å†™čŋ›æ–‡æĄĢïž‰å°ąčƒ―čĄĨäļŠã€‚
  2. å†·įž“å­˜äžšæŠŠæˆæœŽæŽĻ回äŧĢᐆéĒ„įŪ—ã€‚ æĒåĪæœŠå‘―äļ­æ—ķäŧ“åš“æ˜ŊįĐšįš„ïžŒtimeout -k 30 300 侚åœĻäļ‹č――äļ­é€”杀掉äļĪäļŠåŒįš§æĻĄå—åŪ‰čĢ…ïžŒæ—Ĩåŋ—é‡Œį•™äļ‹ qwencode=124 runtime-broker=124ã€‚č€ŒæŠ€čƒ―įšĶåۚæŽĨį€čŊī「äŧŽäŧĢį æ ‘é‡čĢ…čŊĨæĻĄå—」é‚Ģæ˜Ŋäļ€æŽĄåیæ•īäūčĩ–äļ‹č――čŪĄå…ĨäŧĢį†įš„ 120 分钟——æ­Ģæ˜Ŋ ci(verify): provision a Java toolchain in the verify lane so sdk-java PRs are verified every time #13741 æƒģæķˆé™Īįš„äļœčĨŋ。JDK 和 Maven äŧį„ķæ˜Ŋ有äŋéšœįš„所äŧĨ倒退åđ…åšĶ有限PR æ­Ģ文äđŸįĄŪåŪžæŠŦéœē乆įšĶ 20 åˆ†é’Ÿįš„å†·č·Ŋåū„。或čŪļ倞åū—äļščķ…æ—ķå•į‹Žå†™äļ€čĄŒįšĶåŪšïžš124 意å‘ģį€äŧ“åš“æ˜Ŋå†·įš„ïžŒåš”å―“äļšåیæ•īč§Ģæžį•™å‡šéĒ„įŪ—ïžŒč€Œäļæ˜Ŋ重čŊ•åŪ‰čĢ…ã€‚

æĩ‹čŊ•

čŋ™æ˜Ŋäļ€æŽĄæ— äššå€žåŪˆįš„ CI čŋčĄŒïžŒæŒ‰é€šé“č§„åˆ™æˆ‘æēĄæœ‰æž„åŧšæˆ–æ‰§čĄŒæœŽ PR įš„äŧŧä―•äŧĢį â€”â€”äļ‹éĒįš„čŊæŪæ˜Ŋčŋ™äļŠ PR č‡Šå·ąįš„ CI通čŋ‡ API 针åŊđčĒŦåŪĄæäšĪčŊŧå–įš„ã€‚å…ą 61 äļŠ check-runïžš10 äļŠįŧ™å‡šäš†įœŸåŪžįŧ“čŪšïžŒ50 äļŠčĒŦč·ģčŋ‡ïžˆæœŽ diff 朊选äļ­įš„åđģ台äļŽäš‹äŧķįŸĐé˜ĩ1 äļŠæŽ’é˜Ÿã€2 äļŠäŧåœĻčŋ›čĄŒïžŒæēĄæœ‰åĪąčīĨ或取æķˆã€‚

åŊđčŋ™äļŠæ”đåŠĻ最å…ģé”Ūįš„äļĪäļŠâ€”—Lint & Static (ubuntu-latest, Node 22.x) 和 Test (ubuntu-latest, Node 22.x)äļĪåĨ—å·Ĩä―œæĩæĩ‹čŊ•éƒ―č·‘åœĻåŽč€…ïž‰â€”â€”åœĻ我取数æ—ķčŋ˜æēĄįŧ“束。我æēĄæœ‰č―ŪčŊĒį­‰åū…ïž›Qwen Triage Finalize äŧŧåŠĄäžšåœĻ CI įĻģåŪšåŽå°ąåœ°æ”đ写äļŠéĒįš„čĄĻ栞。čŋ›čĄŒäļ­įš„ triage å°ąæ˜ŊæœŽæŽĄčŋčĄŒã€‚

因äļščŋ™äļĪäļŠå°šæœŠåŪŒæˆïžŒæˆ‘æ˜ŽįĄŪ写äœŽæŽĄåŪĄæŸĨäļåŒ…åŦäŧ€äđˆïžšæēĄæœ‰åœĻæœŽåœ°æ‰§čĄŒäŧŧä―•äļ€åĨ—å·Ĩä―œæĩæĩ‹čŊ•äđŸæēĄæœ‰č§‚åŊŸåˆ°į”Ÿäš§č€…或äū›įŧ™č·Ŋåū„įœŸæ­ĢčŋčĄŒã€‚PR æ­Ģæ–‡é‡Œä―œč€…įš„æž”įŧƒæ•°æŪLima node:22-bookwormaarch64各æĻĄå—įš„ mvn test čŪĄæ•°ïž‰æ˜Ŋä―œč€…å…ģäšŽč‡Šå·ąįŽŊåĒƒįš„äļŧ垠äļæ˜Ŋ我åĪč·‘įš„čŊæŪ——而äŒ‰ä―œč€…č‡Šå·ąįš„čŊŽïžŒé‚ĢæŽĄæž”įŧƒæēĄæœ‰å…ˆč·‘ npm run buildïžŒįœŸåŪžé€šé“æ˜Ŋ䞚先构åŧšįš„。

æē™įŪąéŠŒčŊčƒ―坐åŪžå…ķäļ­äļ€éƒĻ分倞åū—čŊīæļ…æ˜Ŋ哊äļ€éƒĻ分@qwen-code /verifyåŊį›īæŽĨč§Ķå‘â€”â€”ä―œč€…æœ‰å†™æƒé™ïžŒæ‰€äŧĨčŋ™äļæ˜ŊäŧĢč·‘ïž‰ã€‚æœŽ diff æ”đįš„æ˜Ŋ .github/、.qwen/、docs/、scripts/tests/æēĄæœ‰įǰ packages/sdk-java/所äŧĨåœĻčŋ™äļŠ PR č‡Šå·ąįš„ verify čŋčĄŒé‡Œ steps.pr.outputs.java äļšå‡ïžŒæ–°æ­ĨéŠĪäļ€äļŠéƒ―äļäžšæ‰§čĄŒã€‚é‚ĢæŽĄčŋčĄŒįĄŪåŪžčƒ―čŊæ˜ŽåĶåŪšå‘įš„éŠŒæ”ķ标准——非 Java įš„ diff äļäļ‹č―― JDK、äļæĒåĪ Maven äŧ“嚓、äļåŪ‰čĢ…åŒįš§æĻĄå———äđŸčƒ―čŊæ˜ŽåļĶį€čŋ™ä𛿖°æ­ĨéŠĪïžˆä―†åĪ„äšŽæƒ°æ€§įŠķæ€ïž‰įš„é€šé“äŧčƒ―č·‘åŪŒã€‚åŪƒæ— æģ•čŊæ˜Žæ­Ģ向č·Ŋåū„ïžšQWEN_VERIFY_JAVA=1 æŠĩčūūäŧĢį†ã€åŒįš§æĻĄå—åŪ‰čĢ…č―åœ°ã€æˆ–įŽŽäšŒæŽĄčŋčĄŒå‡šįް verify Maven repo: hit=true。čŋ™äš›éœ€č́äļ€äļŠæ”đ乆 Java įš„ PRåœĻ合åđķäđ‹åŽã€äļ”į”Ÿäš§č€…å·ēåœĻ main äļŠäŋå­˜čŋ‡ã€‚朊éЌčŊïžšæ­Ģ向č·Ŋåū„原因æ­Ģæ˜ŊåĶ‚æ­Ī——合åđķ前æēĄæœ‰äŧŧä―•é€šé“čƒ―æ‰§čĄŒåŪƒïžŒäļŽå…ķčŪĐäļŠéĒįš„ CI čĄĻæ žæ˜ūåū—å·ēįŧčĶ†į›–ïžŒæˆ‘æ›īæ„ŋ意į›īčŊī。/tmux äļé€‚į”Ļčŋ™é‡ŒæēĄæœ‰ TUI į•ŒéĒ。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1d81dfa7567e789a4b9045a6d0029b7eb528cd5d · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — solid and better thought through than my own baseline; the two nits in Stage 2 are questions about a cold-cache path and a cache-key collision, neither of which can corrupt a verdict.

Going back to what I wrote down before reading the diff: the PR matches it and then beats it. My version would have installed the toolchain somewhere convenient, exported the flag, and called it done. It would have left three holes this one closes — the node user able to plant the readiness marker, a PR-owned POM plugin holding an Actions cache credential, and a strip list maintained by hand that quietly rots the day someone adds a fifth Java module. Those are not things I would have caught in review either, which is the part that raises my confidence rather than lowering it: the reasoning is written down where a future maintainer will find it, and the properties that matter are pinned by tests rather than by comments.

The thing I keep coming back to as the strongest signal is moduleCoordinates(). Deriving the strip list from the POMs on disk means the invariant "no main-built jar of this repo survives into the cache" is enforced by the repository's actual shape instead of by a list someone remembered to update. That is the difference between a design decision and a design decision that holds.

Is every change necessary? Yes. I looked for scope creep specifically and found none — the one edit outside the feature that is not a mechanical baseline bump is a skill paragraph this change made untrue, and leaving it would have told the agent the image ships no JDK.

Six months from now I would thank the author, not curse them. The producer/consumer split mirrors pnpm-store.yml, so there is one pattern to learn rather than two, and the failure modes all degrade in the direction the design doc says they do: a failed download warns and leaves Java under Not covered, a cache miss costs time but not correctness, a failed sibling install is logged with its exit code and the agent is told what to do about it. Nothing here can turn a TypeScript verification into a false fail, which was my main worry going in given the shared job.

What I am not confident about, stated plainly: the positive path has never been observed end to end, and cannot be before merge. The provisioning only runs for a diff touching packages/sdk-java/, so this PR's own verify run exercises the negative criterion and nothing else; the cache-hit criterion additionally needs the producer to have run on main first. That is not a defect in the PR — it is inherent to changing a lane from inside a PR that the lane gates on — and the author says so in the body rather than letting a reader discover it. The two Stage 2 questions are the residue of that: both concern paths (cold cache, producer re-save) that no pre-merge run can reach.

I would merge this. The unit suites and lint were still in flight when I finished, so I am not approving against a result that does not exist yet — approval is deferred until CI lands green on 1d81dfa7567e789a4b9045a6d0029b7eb528cd5d, and the finalize job posts it then. If Test (ubuntu-latest, Node 22.x) comes back red, the new Java suite is the first place to look, since it is the only new thing running there.

äļ­æ–‡čŊŽ

Confidence: 4/5 —— 扎åŪžïžŒč€ŒäŊ”æˆ‘č‡Šå·ąįš„åŸšįšŋæ–đæĄˆæƒģåū—æ›īå‘Ļå…Ļïž›Stage 2 é‡Œįš„äļĪį‚đæ˜Ŋå…ģäšŽå†·įž“å­˜č·Ŋåū„å’Œįž“å­˜ key å†ēįŠįš„į–‘é—ŪäļĪč€…éƒ―äļäžšæąĄæŸ“įŧ“čŪšã€‚

回到我åœĻčŊŧ diff äđ‹å‰å†™äļ‹įš„æ–đæĄˆïžščŋ™äļŠ PR äļŽåۃäļ€č‡īåđķä›īåĨ―ã€‚æˆ‘įš„į‰ˆæœŽäžšæŠŠå·Ĩ具é“ūčĢ…åœĻäļ€äļŠéĄšæ‰‹įš„ä―į―Ū、åŊžå‡šåž€å…ģã€å°ąįŪ—åŪŒäš‹ã€‚é‚Ģäžšį•™äļ‹äļ‰äļŠčŋ™äļŠ PR čĄĨäļŠįš„æīžâ€”—node į”Ļæˆ·čƒ―äžŠé€ å°ąįŧŠæ ‡čŪ°ã€PR č‡Šæœ‰ POM é‡Œįš„æ’äŧķæĄį€ Actions įž“å­˜å‡­æŪ、äŧĨ及äļ€äŧ―靠手å·ĨįŧĪ、åœĻ有䚚新åĒžįŽŽäš”äļŠ Java æĻĄå—é‚ĢåĪĐå°ąæ‚„æ‚„åĪąæ•ˆįš„å‰ĨįĶŧ列čĄĻ。čŋ™äš›æˆ‘åœĻåŪĄæŸĨ里äđŸæœŠåŋ…抓åū—åˆ°ïžŒč€Œčŋ™æ°æ°æ˜Ŋ提éŦ˜æˆ‘äŋĄåŋƒįš„地æ–đäļæ˜Ŋé™ä―ŽïžšæŽĻᐆčĒŦ写åœĻ朊æĨįš„įŧĪč€…äžšįœ‹åˆ°įš„ä―į―Ūč́įī§įš„æ€§čīĻį”ąæĩ‹čŊ•é’‰ä―ïžŒč€Œäļæ˜Ŋ靠æģĻ释。

我反åĪæƒģ到、äđŸčŪĪäļšæœ€čƒ―čŊŽé—ŪéĒ˜įš„äļ€į‚đæ˜Ŋ moduleCoordinates()。äŧŽįĢį›˜äļŠįš„ POM æīūį”Ÿå‰ĨįĶŧ列čĄĻ意å‘ģį€ã€ŒæœŽäŧ“åš“į”ą main 构åŧšįš„ jar äļäžšæŪ‹į•™čŋ›įž“存」čŋ™æĄäļå˜é‡æ˜Ŋį”ąäŧ“åš“įš„įœŸåŪžįŧ“æž„æĨäŋčŊįš„ïžŒč€Œäļæ˜Ŋį”ąæŸäšščŪ°åū—åŽŧæ›īæ–°įš„åˆ—čĄĻæĨäŋčŊã€‚čŋ™å°ąæ˜Ŋäļ€äļŠčŪūčŪĄå†ģåۚäļŽäļ€äļŠčƒ―įŦ™åū—ä―įš„čŪūčŪĄå†ģåۚäđ‹é—īįš„å·ŪåˆŦ。

æŊäļ€åĪ„æ”đåŠĻéƒ―åŋ…čĶå—ïžŸæ˜Ŋįš„ã€‚æˆ‘äļ“é—Ļæ‰ūčŋ‡čŒƒå›ī蔓åŧķæēĄæ‰ū到。į‰đ性äđ‹åĪ–å”Ŋäļ€äļæ˜Ŋ朚æĒ°åŸšįšŋæ›īæ–°įš„æ”đåŠĻæ˜ŊčĒŦčŋ™æŽĄæ”đåŠĻæ”đ写äļšäļåŪžįš„é‚ĢæŪĩæŠ€čƒ―čŊŽâ€”â€”į•™į€åŪƒå°ąäžšå‘ŠčŊ‰äŧĢį†é•œåƒé‡ŒæēĄæœ‰ JDK。

六äļŠæœˆåŽæˆ‘䞚感č°Ēä―œč€…ïžŒč€Œäļæ˜Ŋ驂äŧ–ã€‚į”Ÿäš§č€…/æķˆčīđč€…įš„æ‹†åˆ†äļŽ pnpm-store.yml 同构所äŧĨč́å­Ķįš„æ˜Ŋäļ€äļŠæĻĄåžč€Œäļæ˜ŊäļĪäļŠïž›č€Œä‰€æœ‰åĪąčīĨæĻĄåžéƒ―朝čŪūčŪĄæ–‡æĄĢ所čŊīįš„æ–đ向退化äļ‹č――åĪąčīĨ及告č­Ķ、把 Java į•™åœĻ「朊čĶ†į›–ã€ïžŒįž“å­˜æœŠå‘―äļ­åŠæŸåĪąæ—ķé—īäļæŸåĪąæ­ĢįĄŪæ€§ïžŒåŒįš§æĻĄå—åŪ‰čĢ…åĪąčīĨ侚čŋžé€€å‡šį äļ€čĩ·čŪ°čŋ›æ—Ĩåŋ—、åđķ告čŊ‰äŧĢᐆčŊĨ怎äđˆåŠžã€‚čŋ™é‡ŒæēĄæœ‰äŧŧä―•äļœčĨŋčƒ―æŠŠäļ€æŽĄ TypeScript 驌čŊå˜æˆå‡įš„ failâ€”â€”č€ƒč™‘åˆ°å…ąį”Ļ同äļ€äļŠäŧŧåŠĄïžŒčŋ™æ˜Ŋ我čŋ›æĨæ—ķ最担åŋƒįš„į‚đ。

我äļįĄŪåŪšįš„åœ°æ–đį›īčŊīïžšæ­Ģ向č·Ŋåū„äŧŽæœŠčĒŦįŦŊ到įŦŊ观åŊŸåˆ°ïžŒåˆåđķ前äđŸæ— æģ•č§‚åŊŸã€‚äū›įŧ™é€ŧčū‘及åœĻ diff įǰ到 packages/sdk-java/ æ—ķčŋčĄŒïžŒæ‰€äŧĨčŋ™äļŠ PR č‡Šå·ąįš„ verify čŋčĄŒåŠéЌčŊäš†åĶåŪšå‘æ ‡å‡†ïžŒåˆŦįš„éƒ―æēĄæœ‰ïž›įž“å­˜å‘―äļ­é‚ĢæĄæ ‡å‡†čŋ˜éĒåĪ–čĶæą‚į”Ÿäš§č€…å…ˆåœĻ main äļŠč·‘čŋ‡ã€‚čŋ™äļæ˜Ŋ PR įš„įžšé™·â€”â€”äŧŽäļ€äļŠčĒŦčŊĨ通道é—ĻæŽ§įš„ PR 内éƒĻåŽŧæ”đčŋ™äļŠé€šé“朎æĨå°ąåĶ‚æ­Ī——而äļ”ä―œč€…æ˜Ŋ写åœĻæ­Ģæ–‡é‡Œįš„ïžŒæēĄæœ‰į•™įŧ™čŊŧč€…č‡Šå·ąåŽŧå‘įŽ°ã€‚Stage 2 įš„é‚ĢäļĪäļŠį–‘é—Ūå°ąæ˜Ŋčŋ™äŧķäš‹įš„æŪ‹į•™ïžšåۃäŧŽéƒ―æķ‰åŠåˆåđķ前äŧŧä―•äļ€æŽĄčŋčĄŒéƒ―到äļäš†įš„č·Ŋåū„ïžˆå†·įž“å­˜ã€į”Ÿäš§č€…é‡æ–°äŋå­˜ïž‰ã€‚

我䞚合åđķåŪƒã€‚æˆ‘įœ‹åیæ—ķ单æĩ‹å’Œ lint čŋ˜åœĻč·‘ïžŒæ‰€äŧĨ我äļäžšé’ˆåŊđäļ€äļŠå°šäļå­˜åœĻįš„įŧ“æžœåŽŧæ‰đ准——æ‰đ准æŽĻčŋŸåˆ° CI åœĻ 1d81dfa7567e789a4b9045a6d0029b7eb528cd5d äļŠč―Žįŧŋäđ‹åŽïžŒį”ą finalize äŧŧåŠĄåąŠæ—ķ发凚。åĶ‚æžœ Test (ubuntu-latest, Node 22.x) 变įšĒïžŒįŽŽäļ€äļŠčŊĨįœ‹įš„åœ°æ–đæ˜Ŋ新åĒžįš„ Java æĩ‹čŊ•åĨ—äŧķ因äļšé‚Ģæ˜Ŋé‚Ģ里å”Ŋä–°åĒžįš„čŋčĄŒå†…åŪđ。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1d81dfa7567e789a4b9045a6d0029b7eb528cd5d · re-run with @qwen-code /triage

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /verify

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship — CI landed green after the review. ✅

@doudouOUC
doudouOUC enabled auto-merge October 9, 2026 12:16
@qwen-code-review-bot

qwen-code-review-bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 160 passed · 0 failed · 160 total

Flakiness gate: ✅ 2 changed test file(s) x 5 identical rounds, no divergence

äļ­æ–‡ — åˆĪåŪšïžšâŒ äļé€ščŋ‡ · æŠĨå‘Šäš†å‘įŽ°ïžˆagent åˆĪåŪšïž‰

æē™įŪąéŠŒčŊåœĻ隔įĶŧ、无凭čŊįš„åŪđå™Ļäļ­æ‰§čĄŒäš†čŊĨ PR įš„äŧĢį ïžˆäļŽ base 构åŧš A/B åŊđį…§ã€æ—  mock harness 断čĻ€ã€åŪšå‘é—ĻįĶïž‰ã€‚äŧ…ä―œäļščŊ„åŪĄčŊæŪäļæž„成čŊ„åŪĄã€æ‰đ准或 CI æĢ€æŸĨ。

č„šæœŽæ–­čĻ€ïžš160 通čŋ‡ · 0 åĪąčīĨ · 160 æ€ŧčŪĄ

抖åŠĻé—Ļ✅ 2 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR #13763 deep verification — ci(verify): provision a Java toolchain for sdk-java PRs

Verdict: findings — 160 scripted assertions, 160 passed / 0 failed. The central claim holds and is load-bearing against the base build. Three findings are worth a reviewer's attention: the job-budget line understates the capped worst case by ~31 minutes, the PR's own cited test command cannot pass inside this lane, and the new suite does not pin the Maven sha512 guard. None of the three is a defect in the provisioning logic itself.

Verified head: 1d81dfa7567e789a4b9045a6d0029b7eb528cd5d (git rev-parse HEAD^2), base 4360b003a087fe9e57f6ffbe7ba48795980b59c3. Single commit — git rev-list HEAD^1..HEAD^2 matches the snapshot's one-element commits array, so per-commit attribution is complete despite the depth-2 checkout.

äļ­æ–‡æ‘˜č́

įŧ“čŪšïžšfindings —— 160 æĄč„šæœŽæ–­čĻ€å…ĻéƒĻ通čŋ‡ïžˆ160 passed / 0 failed。äļ­åŋƒäļŧ垠成įŦ‹ïžŒäļ”į›ļåŊđ base 构åŧšæ˜Ŋ load-bearing įš„ïžšåŠæœ‰ diff įǰ到 packages/sdk-java/ æ—ķ才䞚äļ‹č―― JDK、æĒåĪ Maven äŧ“嚓、æģĻå…Ĩ QWEN_VERIFY_JAVA=1ïž›base äū§åœĻ同样čū“å…Ĩäļ‹äļ€åū‹äļæģĻå…Ĩ。钉æ­ŧįš„ Temurin sha256 äļŽ Maven sha512 äļŽįœŸåŪžå‘å–‡äŧķé€å­—čŠ‚į›ļįŽĶactions/cache įš„ SHA įĄŪåŪžč§Ģ析äļš v4.3.0及æĒåĪå…ĨåĢįš„ manifest 里æēĄæœ‰ post:įŧ“æž„äļŠäļåŊčƒ―äŋå­˜įž“存。

äĄ findingsïžš

  1. äŧŧåŠĄéĒ„įŪ—įŪ—é”™äš†ïžˆMedium。æģĻ释写 Java éƒĻ分「â‰Ī ~20mã€ïžŒä―†åŪžæĩ‹ curl --max-time æ˜ŊæŊæŽĄå°čŊ•įš„äļŠé™ïžŒ--retry 3 䞚把åۃäđ˜äŧĨ 4单äļŠäļ‹č――最坏 1206 s = 20.1 分钟å·ēįŧčķ…čŋ‡æ•īæĄéĒ„įŪ—čĄŒã€‚äļĪäļŠäļ‹č――加äļĪæŽĄ timeout -k 30 300 įš„ mvn install 合čŪĄ ~51.2 åˆ†é’ŸïžŒč€Œé™éĒåŠäŧŽ 190 提到 210。按čŊĨ文äŧķč‡Šå·ąįš„åĢåū„最坏 ~231m > 210måŪđå™Ļ侚čĒŦäļ­é€”杀掉æ­ĢåĨ―įŧ•čŋ‡åŪƒč‡Šå·ąæģĻ释里čŊīįš„ ship-what-ran č·Ŋåū„。加äļ€äļŠ --retry-max-time朎镜像 curl 7.88.1 æ”Ŋ持åŪžæĩ‹æŠŠ 18s įš„æŽĒ针降到 8såģåŊčŪĐ「â‰Ī ~20mã€å˜æˆįœŸčŊã€‚
  2. æœŽé€šé“é‡Œč·‘äļå‡š PR č‡Šå·ąįŧ™įš„įŧŋįŊLowïžŒåąžé€šé“čĄŒäļšč€Œéž PR įžšé™·ïž‰ã€‚verify 通道按čŪūčŪĄæŠŠ .qwen 钉回 base åđķčŪūäļšåŠčŊŧïžŒč€Œæ–°æĩ‹čŊ•æ–­čĻ€ HEAD į‰ˆ SKILL.md įš„å†…åŪđ所äŧĨ PR 描čŋ°é‡Œįŧ™å‡šįš„é‚ĢæĄ vitest å‘―äŧĪåœĻ朎通道 1 failed | 8 passed同äļ€å‘―äŧĪåœĻ HEAD 内åŪđįš„ scratch worktree 里 9 passed。抖åŠĻé—Ļ䞚把čŋ™äļŠå˜æ›īčŋ‡įš„æĩ‹čŊ•æ–‡äŧķå―“æˆæŒįŧ­åĪąčīĨæĨč·‘ã€‚
  3. 新æĩ‹čŊ•æēĄæœ‰é’‰ä― Maven sha512 æ ĄéŠŒïžˆSuggestion。变垂įŸĐé˜ĩ 17 caught / 2 survived。删掉 sha512 æ ĄéŠŒåŽæĩ‹čŊ•äŧå…ĻįŧŋindexOf(...) === -1 æŧĄčķģ -1 < readyAt把čŊĨå˜åž‚ä―“įœŸč·‘čĩ·æĨ喂äļ€äļŠæ žåžåیåĨ―ä―†čĒŦæ›ŋæĒčŋ‡įš„ Maven å‘čĄŒåŒ…ïžŒåŪƒäžšå†™äļ‹ ready 标čŪ°ã€‚head 朎čšŦ行äļšæ­ĢįĄŪ同样čū“å…Ĩäļ‹ head 拒įŧåđķ告č­Ķ所äŧĨčŋ™æ˜ŊčĶ†į›–įžšåĢäļæ˜Ŋįžšé™·ã€‚åĶäļ€äļŠåđļå­˜č€…ïžˆåˆ  java -versionįŧåŪžæĩ‹æ˜Ŋå†—ä―™é˜ēåūĄïžšmvn -version äžšå…ˆåĪąčīĨäļĪ者行äļšäļ€č‡ī。

朊čĶ†į›–ïžšįœŸåŪž MariaDB/MySQL failsafe 集成æĩ‹čŊ•、hosted harness、Java 11/17 įŸĐé˜ĩã€įž“å­˜įœŸåŪžå‘―äļ­ïžˆčĶį­‰į”Ÿäš§č€…åœĻ main äļŠč·‘čŋ‡ïž‰ã€actionlint/yamllint朊åŪ‰čĢ…å›šåŪšį‰ˆæœŽäšŒčŋ›åˆķ、äŧĨ及äŧŧä―•įœŸæœšäļŠč·‘ mvn install åŒįš§æĻĄå—朎通道朊 provision JavaïžŒč§ä–‡ïž‰ã€‚

Central claim and A/B

Central claim. A PR diff that touches packages/sdk-java/ causes the verify lane to provision a Java toolchain and hand the agent QWEN_VERIFY_JAVA=1 + JAVA_HOME + MAVEN_ARGS before its clock starts; a diff that does not touch packages/sdk-java downloads nothing and injects nothing; an install failure degrades to a warning with no injection and the agent still runs.

Secondary claims. (1) The restore-only Maven cache can actually be hit by the consumer that restores it. (2) PR-executed code cannot forge the toolchain or reach a credential through it.

Oracle for every cell is an observable artifact — $GITHUB_OUTPUT contents, the ready marker on disk, the emitted QWEN_ENV array, the stub's own call log, the mvn child's environment — never a reading of the YAML.

01-ab-gate-toolchain-modules.png shows the arm-shape, gate and toolchain cells as they printed; 02-mutation-matrix-and-survivor-adjudication.png shows the matrix and the survivor adjudication.

Gate A/B — 8 file-list fixtures × both arms

The verbatim slice of Resolve PR and snapshot metadata was taken between the same two anchors in both arms (if grep -qE '(^|/) â€Ķ # Snapshot PR metadata), so the PR's hunk is the only difference inside it, and executed under bash --noprofile --norc with the step's own set -euo pipefail.

fixture base java head java head decision=na
this PR's real file list (no sdk-java) false false –
packages/sdk-java/qwencode/â€Ķ + pom false true –
sdk-java only, deeply nested test file false true –
packages/core/src/index.ts false false –
docs-only (README.md, docs/index.md) false false yes
near-miss prefixes (packages/sdk-java-docs/, sdk-java/, packages/sdk-javax/) false false –
packages/sdk-java/README.md only false false yes (docs-only wins)
401 files, sdk-java entry last (SIGPIPE shape) false true –

16/16 cells as predicted: head flips on exactly the sdk-java inputs, base never provisions for any input. The last row is the one the code comment claims to defend (grep <<< rather than a pipe, which under pipefail previously misclassified code PRs as docs-only) — it holds with the match at the end of a 401-line list.

Toolchain step — verbatim, against the real published archives

The step's run: block was executed as extracted, with a stub curl serving the real archives downloaded from the two pinned URLs, and real sha256sum/sha512sum/tar/find/java/mvn.

cell exit ready prefix dir oracle
success (real archives) 0 written present openjdk version "21.0.12.1", Apache Maven 3.9.11, 2 curl calls, no warning
JDK archive corrupted 0 absent removed ::warning::Java toolchain install failed
Maven archive corrupted 0 absent removed same warning
download fails (curl exit 7) 0 absent removed same warning

The three failure cells are the load-bearing proof that the checksum gate is not decorative: a single flipped byte in either archive removes the marker, which is what the agent's env injection keys on. All four exit 0, so a Java failure can never fail a TypeScript verification.

Agent env injection — verbatim QWEN_ENV region, both arms

cell head entries QWEN_VERIFY_JAVA PATH entries base arm
ready present 27 1 2, java one last no injection
ready absent 23 absent 1 no injection

The base arm was run in both cells, including the positive one, per the symmetric-control rule. The comment's claim that "env applies QWEN_ENV left to right, so this PATH overrides the one above" was measured independently: env -i PATH=/first PATH=/second printenv PATH → /second.

Sibling module install — credential stripping measured at the real seam

The mvn stub dumps its own environment, i.e. what survives runuser -u node -- env -u â€Ķ. ACTIONS_RUNTIME_TOKEN, ACTIONS_RUNTIME_URL and ACTIONS_CACHE_URL were deliberately planted in the parent with sentinel values so a missing -u would be observable rather than assumed absent.

cell exit mvn calls last line of java-prepare.log planted credential in child
toolchain missing 0 0 toolchain=missing n/a
ready, mvn succeeds 0 2 qwencode=0 runtime-broker=0 none
ready, mvn fails 0 1 each, both fail qwencode=1 runtime-broker=1 none

Both installs use the restored repo as -Dmaven.repo.local, qwencode gets -Dgpg.skip + -Dmaven.javadoc.skip, runtime-broker gets -Dspotbugs.skip, and the step summary carries verify Maven repo: hit=â€Ķ key=â€Ķ in all three cells including the cache-miss one.

Live-lane negative arm (production measurement, no harness)

This PR touches .github/workflows/, .qwen/skills/, docs/ and scripts/ — not packages/sdk-java/ — so the gate was off for this very run, and the container is a live sample of the lane:

  • no QWEN_VERIFY_JAVA, JAVA_HOME or MAVEN_ARGS in the agent's /proc/self/environ; no java/mvn on PATH
  • /opt/verify-java never created; /__w/_temp/verify-maven-repo never created; no java-prepare.log beside $QWEN_VERIFY_CONTEXT
  • $RUNNER_TEMP is /__w/_temp (derived from QWEN_VERIFY_CONTEXT=/__w/_temp/verify-context/pr.json), and /home/runner does not exist in the container

Corrections to the PR description

Labelled as corrections to the description, not requests to change code.

  1. "The node:test file also has pre-existing macOS failures â€Ķ the timeout assertion itself passed." On this Linux lane the whole file is green: # tests 125 / # pass 125 / # fail 0. The macOS caveat does not apply here and could mislead a reader into discounting a red node --test run on Linux.
  2. "The pinned x64 Temurin archive checksum matched the published file." Confirmed independently, and extended: the Temurin sha256 and the Maven sha512 both match the files the pinned URLs actually serve, byte for byte. Both archive layouts also satisfy the step's own post-extract checks (jdk-21.0.12.1+1/, apache-maven-3.9.11/).
  3. "qwencode plus runtime-broker installed from the merge ref" is accurate for the consumer, but the producer populates the repo from all four modules (managed-agent-server and client via verify). The strip list correctly covers all four artifactIds, so an agent testing those two modules has their third-party dependencies but not their own artifacts — which the HEAD skill text does tell the agent to reinstall. Not a defect; the description just understates what the producer warms.

Findings

1. Medium — the job budget line understates the capped worst case by ~31 minutes

.github/workflows/qwen-triage.yml, the timeout-minutes comment block:

#   java toolchain + Maven repo
#   + sibling modules            â‰Ī ~20m, and only when the diff touches
#                                packages/sdk-java/. curl --max-time caps
#                                each download; `timeout 300` caps each
#                                mvn install
#   worst case                   ~200m  ⇒ 210 leaves 10m of headroom.

curl --max-time caps each attempt, and --retry 3 retries on a timeout, so the cap multiplies. Measured in this image (curl 7.88.1) against a server that accepts and never replies, with the flags scaled down 100× (curl-retry-measurement.txt):

--retry 3 --retry-delay 2 --max-time 3   → elapsed=18s   (= 4×3 + 3×2 exactly)
--retry 3 --retry-delay 2 --max-time 3 --retry-max-time 7 → elapsed=8s

At the step's real values:

item cap
one download (1+3)×300 + 3×2 1206 s = 20.1 m
two downloads (JDK + Maven) 40.2 m
two timeout -k 30 300 sibling installs 660 s = 11.0 m
Java worst case ~51.2 m (line says â‰Ī ~20 m)
cache restore of the Maven repo not priced at all

A single download's cap already exceeds the whole Java budget line. Against the file's own pre-PR items (120 m agent + 15 m install/build + 40 m gate + 5 m misc = 180 m) the worst case is ~231 m versus a 210 m limit — i.e. the 20-minute raise does not cover the addition it was raised for. The consequence is the one the comment itself names: the job timeout kills the container mid-run and bypasses the ship-what-ran path, so a Java PR in a network-degraded window loses its report entirely. The timeout-minutes >= 210 assertion in .github/scripts/qwen-triage-workflow.test.mjs encodes the understated figure, so the existing guard cannot catch this.

This is a budget-robustness finding, not a correctness bug: it needs a black-hole network (packets dropped, not refused) to reach, and the ordinary failure mode — a fast 404 or DNS error — is cheap. But that regime is exactly what --max-time --retry exists for, and this file's convention elsewhere is to sum the caps (the flakiness-gate line itemises -k 10 30, -k 30 120, -k 30 600).

Measured candidate fix (one flag per download)

--retry-max-time exists in this image's curl 7.88.1 and caps the whole retry loop. Adding --retry-max-time 300 to both curl invocations makes each download's cap 300 s, so the Java worst case becomes ~5 m + ~5 m + 11 m ≈ 21 m — the stated "â‰Ī ~20m" becomes true and no limit raise is needed. Verified direction: the scaled probe above drops from 18 s to 8 s when the flag is added, with the same exit code 28 and the same warning path. Not applied to the PR; the arithmetic above is what a maintainer should re-check.

-          if curl -fsSL --retry 3 --retry-delay 2 --max-time 300 -o "$work/jdk.tar.gz" "$TEMURIN_URL" \
+          if curl -fsSL --retry 3 --retry-delay 2 --max-time 300 --retry-max-time 300 -o "$work/jdk.tar.gz" "$TEMURIN_URL" \

2. Low — the PR's cited test command cannot pass inside the verify lane

The Reviewer Test Plan gives:

npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-triage-workflow.test.js \
  -t "qwen-triage verify Java toolchain|strips GitHub command files"

Run verbatim in this lane: 1 failed | 8 passed | 168 skipped. The failure is expect(verifySkill).toContain('QWEN_VERIFY_JAVA=1'). Run in a scratch worktree at HEAD content: 9 passed.

The cause is the lane, not the PR: Pin agent inputs from base replaces .qwen with git archive HEAD^1 -- .qwen and re-pins it root-owned and read-only (dr-xr-xr-x root root, verified), with the workflow's own note that "A PR that edits the skill is verified with the base version by design". So a PR that edits the verify skill is, by construction, unable to satisfy its own skill-content assertions in this lane.

Two consequences worth naming:

  • The Test Plan step is not performable here. A reviewer following it inside a verify run will see red and has no way to tell the lane artifact from a real regression without repeating the run against unpinned content.
  • The flakiness gate's signal for this file is degraded. scripts/tests/qwen-triage-workflow.test.js is a changed test file, so the gate re-runs it in the pinned tree across identical rounds. It will see the same failure every round — "no divergence", so the gate stays quiet — but the file can no longer distinguish a genuine break from the pinning artifact.

Not a request to weaken the pin, which is a real security property. A one-line note in the Test Plan ("these four assertions require an unpinned .qwen; inside the verify lane they fail by design") would close the gap, or the skill-content assertions could read the file from HEAD via git rather than from the working tree.

3. Suggestion — the new suite does not pin the Maven sha512 verification

Mutation matrix: 17 caught / 2 survived / 19, unmutated control green (9 passed), and a positive control (timeout-minutes: 210 → 190) caught in the same file as the survivors. Two mutants needed re-anchoring after their first anchors matched 2× and 0×; both were caught once applied.

survivor classification evidence
delete sha512sum --check --status coverage gap suite stays 9 passed; driving the mutant build with a well-formed but substituted Maven distribution and the real pin still in force writes ready (ready=true), where head refuses it (ready=false + warning)
delete "$prefix/jdk/bin/java" -version redundant defence driving the mutant with a broken JVM gives ready=false, identical to head, because mvn -version fails first — and that line is pinned, via expect(javaHomeAt).toBeLessThan(toolchain.indexOf('"$prefix/maven/bin/mvn" -version'))

The asymmetry is the finding: the Temurin sha256 check is pinned by an explicit toBeGreaterThan(-1), while the Maven sha512 check is only pinned by an ordering assertion of the shape indexOf(x) < readyAt, which -1 satisfies. The same shape leaves java -version unpinned, though there a sibling assertion covers the hazard. Head's behaviour is correct — the tamper cell in the A/B table above proves the sha512 gate fires — so this is a test to write, not code to fix. The fixture that would go red: assert expect(toolchain.indexOf('sha512sum --check --status')).toBeGreaterThan(-1) alongside the existing sha256 line.

Caught mutants, for completeness: timeout floor; sha256 removal; mvn -version removal; unanchored grep; herestring→pipe; cache/restore→cache; dropped continue-on-error; dropped acp-sdk from the strip list; removed QWEN_VERIFY_JAVA=1; removed MAVEN_ARGS; dropped timeout -k 30 300; dropped the ACTIONS_RUNTIME_* strip; producer cache path changed; producer moved to ubuntu-latest; dropped -Dspotbugs.skip; exit 1 on download failure; if true in place of the ready-marker guard.

Nit — one java-prepare.log state the skill contract does not describe

Read from the code, not measured: if chown -R node:node "$repo" fails, the step logs toolchain=chown-failed and exits before any mvn, so the log never gets the qwencode=<n> runtime-broker=<n> last line — yet /opt/verify-java/ready still exists, so the agent does receive QWEN_VERIFY_JAVA=1. The HEAD skill text tells the agent the exit codes are the last line and to reinstall "if either code is non-zero", which does not cover a missing line. Low likelihood (needs a non-root step or a read-only $RUNNER_TEMP).

Not covered

  • Any real mvn execution. The gate is off for this PR, so no JDK was provisioned in this lane and QWEN_VERIFY_JAVA is unset — per the base-pinned skill, the Java side is not covered. The mvn invocations were verified as command lines and environment through a stub at the real runuser seam; no module was compiled or tested. The PR description's own mvn test counts (208 / 754 / 1185) were not reproduced.
  • A real cache hit. Requires the producer to have saved on main first; the PR says so too. What was proven instead is that the mechanism can fire: getCacheVersion in @actions/cache@4.1.0 (the version the pinned action SHA depends on) hashes the literal resolved paths + compression method + salt; producer and consumer share runs-on, container image and options: '--init --user node' with the already-in-production pnpm-store.yml pair; neither image ships a zstd binary, so both fall back identically; and no /home/runner host path exists here, so $RUNNER_TEMP and ${{ runner.temp }} are the same directory (/__w/_temp). The restore-only entrypoint's own manifest has no post: step while the full action's has post: 'dist/save/index.js' — read from the manifests at the pinned SHA, not from the PR's description. Residual not measured: an actual round-trip through the cache service.
  • Database-backed Java tests, the hosted harness, the Java 11/17 matrix, and re-running Java tests through the flakiness gate — all declared out of scope by the PR; not attempted.
  • actionlint and yamllint. Not installed in this container and node scripts/lint.js --setup was not run for budget reasons. bash -n was run on all three producer run: blocks (all OK), and the consumer's changed blocks were executed, which is stronger. The size ratchet was run and its liveness proven (planting 100 for verify-maven-repo.yml yields exit 1 with grew to 5575 bytes); as committed both entries are byte-exact (362377, 5575) and the gate exits 0.
  • Producer end-to-end. verify-maven-repo.yml was parsed, syntax-checked and its strip list checked against the real coordinates (acp-sdk, qwen-managed-agent-server, qwen-managed-runtime-broker, qwencode-sdk, all com.alibaba, all fixed non-SNAPSHOT versions — which is what makes the strip necessary). It was never executed: that needs a cold JDK plus a full Spring Boot resolve. Its day-one cost was not modelled either; the trigger is narrow (packages/sdk-java/*/pom.xml or the workflow itself, on main), so arrival rate is low, but confirming that needs gh api event history, which this token-free environment does not have.
  • One harness limitation, stated for the record. The toolchain and modules scripts were executed with prefix=/opt/verify-java rewritten to a sandbox path, because /opt is root-owned and this agent is uid 1000 with no CAP_SYS_ADMIN (unshare → Operation not permitted). Everything else is byte-identical to the extracted step. The security property the rewrite gives up was measured directly against the live lane instead: mkdir -p /opt/verify-java as node → Permission denied, while run: steps in this job do execute as root (/etc/apt/sources.list.d/github-cli.list is root-owned), so the step can write /opt and PR code cannot forge the marker.

Methodology

Everything ran inside the lane's own node:22-bookworm container at the merge ref (head 1d81dfa7, base 4360b003), as uid 1000, with the pre-built tree already present. Three harnesses, all kept in this directory as .mjs so a maintainer can rerun them: ab-java-toolchain.mjs (103 assertions — arm shape, gate A/B over 8 fixtures × 2 arms, 4 toolchain cells against the real published archives, agent-env injection in both arms, sibling-install cells, cache identity) parses both workflow revisions from git show with the yaml package, extracts the run: blocks verbatim and executes them under bash --noprofile --norc keeping each step's own set line, stubbing only external tools (curl, runuser, mvn, chown) and asserting on their recorded call logs and child environments; followups.mjs (11) re-applies the two mis-anchored mutants and adjudicates both survivors by building and driving the mutant scripts against stand-in archives; gates.mjs (46) covers the supply-chain pins against the real downloaded files, the actions/cache manifests and getCacheVersion source at the pinned SHA, the strip list against the real POM coordinates, the repo's own gates and the size ratchet plus its liveness, and the live-lane negative arm. Repo gates were run separately: node --test .github/scripts/qwen-triage-workflow.test.mjs (125/125) and the full scripts/tests/qwen-triage-workflow.test.js + scripts/tests/workflow-size.test.js at HEAD content in a scratch worktree (400/400), plus the PR's cited filter both in the pinned lane tree and unpinned. Raw logs are ab-java-toolchain.log, mutation-matrix.log, followups.log, gates.log, gate-*.log, curl-retry-measurement.txt; third-party manifests and the real archive hashes are under vendor/.

Flakiness gate log

rounds=5 files=2 skipped=0
file .github/scripts/qwen-triage-workflow.test.mjs: (cd .) node --test ./.github/scripts/qwen-triage-workflow.test.mjs
file scripts/tests/qwen-triage-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/qwen-triage-workflow.test.js


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  .github/scripts/qwen-triage-workflow.test.mjs: PPPPP
  scripts/tests/qwen-triage-workflow.test.js: PPPPP

verdict: pass
summary: 2 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · .github/scripts/qwen-triage-workflow.test.mjs: P (exit 0)
round 1 · scripts/tests/qwen-triage-workflow.test.js: P (exit 0)
round 2 · .github/scripts/qwen-triage-workflow.test.mjs: P (exit 0)
round 2 · scripts/tests/qwen-triage-workflow.test.js: P (exit 0)
round 3 · .github/scripts/qwen-triage-workflow.test.mjs: P (exit 0)
round 3 · scripts/tests/qwen-triage-workflow.test.js: P (exit 0)
round 4 · .github/scripts/qwen-triage-workflow.test.mjs: P (exit 0)
round 4 · scripts/tests/qwen-triage-workflow.test.js: P (exit 0)
round 5 · .github/scripts/qwen-triage-workflow.test.mjs: P (exit 0)
round 5 · scripts/tests/qwen-triage-workflow.test.js: P (exit 0)

Evidence images

01-ab-gate-toolchain-modules

02-mutation-matrix-and-survivor-adjudication

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

— Qwen Code · sandboxed verification

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at head f4841fe — LGTM.

The trust model is the load-bearing part of a verify-lane change, and it holds up:

  • Cache poisoning is closed on both sides: the consumer is restore-only (a save step in a job that executes PR code would let a PR write the shared cache), and the producer strips this repo's own artifacts before saving — necessary because the module versions are fixed, not SNAPSHOT, so a main-built jar would otherwise be what Maven resolves for the PR tree.
  • The ready marker lives under root-owned /opt, so the node user (which runs PR code) cannot plant it, and the toolchain install happens pre-checkout with no PR tree on disk — the same supply-chain surface as the existing runner-tools step.
  • Credential hygiene in the sibling-install step is explicit: GITHUB_TOKEN/GH_TOKEN blanked, ACTIONS_* cache tokens unset under runuser, each mvn capped by timeout, and the step always exits 0 so a sibling failure degrades Java to Not covered instead of failing a TypeScript verdict.
  • The ^packages/sdk-java/ gate is deliberately narrower than sdk-java.yml's path list, and the comment says why (those paths are mostly core/CLI, and this lane cannot host the database-backed ITs anyway) — other PRs download nothing.
  • The SKILL.md contract tells the agent the non-SNPAPSHOT A/B trap (reinstall siblings from the base worktree, or a copied repo) and pins the failure semantics (QWEN_VERIFY_JAVA unset with an sdk-java diff means the toolchain failed — report Not covered, do not self-provision).
  • The 190→210 timeout raise states its cost in the comment (an ECS slot held ~3.5h instead of 1h), which is the right way to land it.

Producer/consumer runner and image are matched so cache entries are restorable, and the workflow-test pins (including the strip markers) keep the two copies from drifting. CI on this head: no failures; several lanes still running.

@doudouOUC
doudouOUC added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit b9fa95c Oct 9, 2026
31 of 32 checks passed
@doudouOUC
doudouOUC deleted the feat/verify-java-toolchain branch October 9, 2026 15:00
wenshao pushed a commit to chiga0/qwen-code that referenced this pull request Oct 9, 2026
â€Ķe guidance (QwenLM#13791) (QwenLM#13792)

PR QwenLM#13763 provisioned a JDK 21 + Maven toolchain for sdk-java diffs and
pinned the verify-pr skill to never claim the verify image ships no JDK,
but the "Java-centred PRs" bullet added by the concurrently merged
QwenLM#13756 still made exactly that claim, failing the qwen-triage workflow
test on main. Repoint the bullet at the QWEN_VERIFY_JAVA=1 gate in the
environment contract and keep the probe-then-Not-covered guidance for a
failed toolchain install.

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.25.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(verify): provision a Java toolchain in the verify lane so sdk-java PRs are verified every time

3 participants