Skip to content

fix(managed-agent): name a message-less row failure in the scan summary (#15700 item 4) - #15725

Merged
yiliang114 merged 1 commit into
mainfrom
fix/issue-15700-message-less-row-failure
Oct 28, 2026
Merged

yiliang114 merged 1 commit into
mainfrom
fix/issue-15700-message-less-row-failure

Conversation

@yiliang114

Copy link
Copy Markdown
Collaborator

What this PR does

One item from #15700's deferred list: the scanner's bounded failure summary now names the exception class when a non-first row failure carries no message. Previously such a row rendered as failure=null, with no class and no other record anywhere, because the summary is the only place an additional row failure is reported.

Why it's needed

#15632 replaced unbounded per-row stacks with one bounded summary. The sample it keeps is the only record of every failure except the first — so a message-less exception was recorded as the literal string failure=null, which identifies neither the row's cause nor its type. That is the one item in the deferred list that loses information rather than merely formatting it differently.

Reviewer Test Plan

How to verify

Non-UI. The change is confined to the sample string construction:

  • With the previous wording, a tick where the second row throws a message-less IllegalStateException logs failure=null; with this change it logs failure=java.lang.IllegalStateException.
  • Failures that do carry a message keep their existing rendering, so the summary format for the common case is unchanged.

aMessageLessRowFailureKeepsItsIdentityInTheSummary pins both halves, and I confirmed it fails against the unpatched construction (the assertion on failure= sees null) and passes after; commonClaimFailuresProduceOneBoundedSummary still passes unchanged.

Evidence (Before & After)

N/A — non-UI. Before (reverted hunk, same test): sample=[â€Ķ failure=claim store down, â€Ķ failure=null]; after: failure=java.lang.IllegalStateException. Local: both summary tests green, Checkstyle 0 violations.

Tested on

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

Environment (optional)

OpenJDK 21, Maven 3.10.

Risk & Scope

Linked Issues

Refs #15700 — item 4 only; the other eight deferred suggestions stay deferred.

äļ­æ–‡čŊŽ

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

å΄ᐆ #15700 åŧķ后æļ…单äļ­įš„äļ€éĄđïžšæ‰Ŧ描å™Ļįš„æœ‰į•ŒåĪąčīĨæą‡æ€ŧåœĻ非éĶ–äļŠčĄŒåĪąčīĨæēĄæœ‰ message æ—ķæ”đäļščŪ°å―•åž‚åļļįąŧ名。æ­Ī前čŋ™į§æƒ…å†ĩ侚æļ翟“成 failure=nullæ—ĒæēĄæœ‰įąŧ名、äđŸæēĄæœ‰äŧŧä―•å…ķåۃčŪ°å―•â€”â€”å› äļšæą‡æ€ŧæ­Ģæ˜ŊéĒåĪ–čĄŒåĪąčīĨå”Ŋäļ€įš„čŪ°å―•ä―į―Ū。

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

#15632 į”ĻäĄæœ‰į•Œæą‡æ€ŧæ›ŋæĒäš†é€čĄŒæ— į•Œå †æ ˆã€‚åۃäŋį•™įš„æ ·æœŽæ˜Ŋé™ĪįŽŽäļ€äļŠäđ‹åĪ–æŊäļŠåĪąčīĨįš„å”Ŋäļ€čŪ°å―•ïžŒå› æ­Ī无 message įš„åž‚åļļčĒŦčŪ°æˆå­—éĒ量 failure=nullæ—Ēäļčƒ―čŊŽåŽŸå› ã€äđŸäļčƒ―čŊŽįąŧ型。čŋ™æ˜Ŋåŧķ后æļ…单äļ­å”Ŋäļ€ã€ŒäļĒäŋĄæŊã€č€Œéžã€Œæ žåžäļåŒã€įš„äļ€éĄđ。

čŊ„åŪĄč€…éŠŒčŊčŪĄåˆ’

åĶ‚ä―•éŠŒčŊ

非 UI。æ”đåŠĻäŧ…限样朎字įŽĶäļēæž„造

  • į”Ļ旧写æģ•æ—ķïžŒįŽŽäšŒäļŠčĄŒæŠ›æ—  message įš„ IllegalStateExceptionæ—Ĩåŋ—äļš failure=nullïž›æ”đåŠĻ后äļš failure=java.lang.IllegalStateException。
  • åļĶ message įš„åĪąčīĨäŋæŒåŽŸæœ‰æļ翟“åļļč§æƒ…å―Ēæ žåžäļå˜ã€‚

aMessageLessRowFailureKeepsItsIdentityInTheSummary 同æ—ķé’‰ä―čŋ™äļĪ半我銌čŊčŋ‡åۃåœĻ朊äŋŪåĪįš„å†™æģ•äļ‹åĪąčīĨfailure= åĪ„äļš null、äŋŪåĪåŽé€ščŋ‡ïž›commonClaimFailuresProduceOneBoundedSummary äļå—å―ąå“äŧé€ščŋ‡ã€‚

čŊæŪ前后åŊđæŊ”

N/A —— 非 UI。äŋŪåĪå‰ïžˆå›žé€€čŊĨ hunk同äĩ‹čŊ•sample=[â€Ķ failure=claim store down, â€Ķ failure=null]ïž›äŋŪåĪåŽïžšfailure=java.lang.IllegalStateException。朎地äļĪäļŠæą‡æ€ŧæĩ‹čŊ•通čŋ‡ïžŒCheckstyle 0 čŋč§„。

æĩ‹čŊ•åđģ台

macOS ✅Windows / LinuxïžšN/A。

įŽŊåĒƒïžˆåŊ选

OpenJDK 21、Maven 3.10。

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

  • äļŧč́éĢŽé™Đæˆ–æƒčĄĄïžšåŠæ”đčŊŠæ–­å­—įŽĶäļēïž›æą‡æ€ŧįš„čŪĄæ•°äļŽäļĨ重åšĶčŊ­äđ‰æœŠåŠĻfix(managed-agent): close the deferred scanner summary-diagnostics suggestions from #15632 #15700 å…ķä―™å…ŦéĄđäŧäŋæŒåŧķ后。
  • 朊銌čŊ / äļåœĻ范å›īïžšäļæķ‰åŠ MySQL 或 Hosted profileïž›äŧŧä―•éžæ—Ĩåŋ—č·Ŋåū„æ— čĄŒäļšå˜åŒ–。
  • į ī坏性变æ›ī / čŋį§ŧčŊŽïžšæ— ã€‚åļĶ message åĪąčīĨįš„æ—Ĩåŋ—文朎äļå˜ã€‚

å…ģ联 Issue

Refs #15700 —— äŧ…įŽŽ 4 éĄđïž›å…ķä―™å…ŦæĄåŧķ后åŧščŪŪįŧ§įŧ­äŋæŒåŧķ后。

The bounded summary recorded only getMessage(), so an additional row
failure whose exception carried no message rendered as failure=null with
no class — and since the summary is the only record of a non-first
failure, that row's failure had no identity anywhere (#15700 item 4).

Name the class in that case. Pinned by a test that fails on the previous
wording (the tick logged failure=null) and passes now.
@qwen-code-review-bot

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

Copy link
Copy Markdown
Collaborator

⚠ïļ Deferred approval withheld — 3 PR CI workflow run(s) on f8d70f2 did not finish green; see the updated table in the Stage 2 comment. Re-run @qwen-code /triage after fixes. finalize run

⚠ïļ åŧķčŋŸåŪĄæ‰đå·ē搁į―Ū â€”â€” f8d70f2 有 3 äļŠ PR CI workflow 朊äŧĨįŧŋč‰ēåŪŒæˆïžŒčŊĶ见 Stage 2 čŊ„čŪšäļ­å·ēæ›īæ–°įš„čĄĻ栞。äŋŪåĪåŽåŊ重新čŋčĄŒ @qwen-code /triage。æŸĨįœ‹ finalize čŋčĄŒ

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present, Risk & Scope filled in, and the äļ­æ–‡čŊŽ is a complete parallel translation rather than a summary.

Problem: real, and I verified it in the code rather than taking the description's word for it. In AutomationScanner.tick(), the first row failure is kept in failure and rethrown, so it still carries its stack. Every additional failure only bumps additionalFailures — the bounded 3-entry failureSample is genuinely the only place its identity is recorded, and that WARN is throwable-free (commonClaimFailuresProduceOneBoundedSummary already pins getThrowableProxy() as null). The sample was built from error.getCause().getMessage(), so a cause with no message rendered the literal failure=null: no message, no class, no stack, nowhere else. That is deterministic behaviour, not a hypothetical. It is also an agreed follow-up rather than a drive-by idea — it is item 4 (R2-4) of #15700, still open, which explicitly asks for "a small scoped fix with the pinning test that proves it".

Direction: aligned. This is diagnostics quality on a failure summary that exists precisely to illuminate a sustained outage — the case where a failure=null row costs the most. It touches no public contract, no schema, no auth or tenant boundary, and no API surface. Taking one item from a nine-item deferred list and leaving the other eight deferred is the right instinct under the repo's "don't let review rounds balloon the PR" rule. No CHANGELOG reference applies — this is a server-side log string in an unreleased module, not a user-facing CLI behaviour.

Size: not applicable. Neither changed path matches the core-module patterns (packages/core/src/**, or auth/providers/models/config/tools/services under a package's src/), and the change stays inside one package. For the record: 9 production lines (AutomationScanner.java, +8/−1) and 39 test lines (AutomationScannerTest.java, +39/−0), no generated or schema files.

Approach: this is what I would have written. Read from the problem statement alone, my proposal was to fall back to the exception's class name at the sample-construction site and nowhere else — which is exactly the shape here. Scope is minimal: one expression, one behaviour, one pinning test, no drive-by edits or formatting churn, and no attempt to fold in the neighbouring deferred items (R2-3's sample/count population mismatch sits three lines away and is correctly left alone). Two small non-blocking thoughts go in the code review below rather than here.

Risk: no elevated risk signals. Neither file matches the revert-correlated high-risk paths, so no extra review depth is warranted beyond the normal pass.

Moving on to code review. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪ

æĻĄæŋåیæ•ī ✓ —— åŋ…需įŦ čŠ‚é―å…ĻRisk & Scope å·ēåĄŦ写äļ­æ–‡čŊŽæ˜Ŋ逐æŪĩåŊđåš”įš„åیæ•īįŋŧčŊ‘č€Œéžæ‘˜čĶã€‚

é—ŪéĒ˜ïžšįœŸåŪžå­˜åœĻåđķäˆ‘æ˜ŊčŊŧäŧĢ᠁įĄŪčŪĪįš„ïžŒäļæ˜Ŋį…§æŠ„ PR 描čŋ°ã€‚ AutomationScanner.tick() äļ­ïžŒįŽŽäļ€äļŠčĄŒåĪąčīĨ侚čĒŦ存å…Ĩ failure åđķ重新抛凚因æ­ĪäŧåļĶå †æ ˆïž›č€Œå…ķä―™æŊäļŠåĪąčīĨ及æ˜ŊčŪĐ additionalFailures 加äļ€ â€”â€” æœ‰į•Œįš„ 3 æĄ failureSample įĄŪåŪžæ˜Ŋå…ķčšŦäŧ―å”Ŋäļ€įš„čŪ°å―•ä―į―Ūäļ”čŊĨ WARN äļæšåļĶ throwablecommonClaimFailuresProduceOneBoundedSummary å·ēé’‰ä― getThrowableProxy() äļš nullïž‰ã€‚æ ·æœŽåŽŸå…ˆį”ą error.getCause().getMessage() 构造所äŧĨ无 message įš„ cause 侚æļ翟“成字éĒ量 failure=nullïžšæēĄæœ‰ message、æēĄæœ‰įąŧ名、æēĄæœ‰å †æ ˆïžŒåˆŦåĪ„äđŸæēĄæœ‰ã€‚čŋ™æ˜ŊįĄŪåŪšæ€§čĄŒäļšïžŒäļæ˜Ŋ假čŪū。åۃäđŸæ˜Ŋäļ€äļŠå·ēčūūæˆå…ąčŊ†įš„后įŧ­éĄđïžŒč€Œéžäļīæ—ķčĩ·æ„ —— åģ #15700äŧäļš openïž‰įš„įŽŽ 4 éĄđR2-4čŊĨ issue 明įĄŪčĶæą‚ã€Œäļ€äļŠå°čŒƒå›īäŋŪåĪïžŒåđķ配äļŠčƒ―čŊæ˜ŽåŪƒįš„é’‰ä―æĩ‹čŊ•」。

æ–đ向åŊđé―ã€‚ čŋ™æ˜Ŋ针åŊđåĪąčīĨæą‡æ€ŧįš„čŊŠæ–­čīĻ量æ”đčŋ›ïžŒč€ŒčŊĨæą‡æ€ŧ存åœĻįš„æ„äđ‰æ­Ģæ˜Ŋį…§äšŪ持įŧ­æ€§æ•…éšœ —— 恰恰æ˜Ŋ failure=null äŧĢäŧ·æœ€éŦ˜įš„圚æ™Ŋ。æ”đåŠĻäļæķ‰åŠå…Žå…ąåĨ‘įšĶ、schema、é‰īæƒæˆ–į§Ÿæˆ·čūđį•ŒïžŒäđŸäļæķ‰åŠäŧŧä―• API éĒ。äŧŽäđéĄđåŧķ后æļ…单äļ­å–äļ€éĄđ、å…ķä―™å…ŦéĄđįŧ§įŧ­åŧķ后įŽĶ合äŧ“嚓「äļč́čŪĐčŊ„åŪĄč―ŪæŽĄæŠŠ PR 撑åĪ§ã€įš„č§„åˆ™ã€‚CHANGELOG 无åŊđåš”å‚č€ƒ —— čŋ™æ˜Ŋ朊发åļƒæĻĄå—äļ­įš„æœåŠĄįŦŊæ—Ĩåŋ—å­—įŽĶäļēäļæ˜ŊéĒ向į”Ļæˆ·įš„ CLI 行äļšã€‚

规æĻĄïžšäļé€‚į”Ļ。 äļĪäļŠæ”đåŠĻč·Ŋåū„éƒ―äļåŒđ配æ ļåŋƒæĻĄå—č§„åˆ™ïžˆpackages/core/src/**或某äļŠåŒ… src/ äļ‹įš„ auth/providers/models/config/tools/servicesä”đåŠĻ限䚎单äļŠåŒ…内。čŪ°å―•äļ€äļ‹ïžšį”Ÿäš§äŧĢ᠁ 9 čĄŒïžˆAutomationScanner.java+8/−1æĩ‹čŊ• 39 čĄŒïžˆAutomationScannerTest.java+39/−0ïž‰ïžŒæ— į”Ÿæˆæ–‡äŧķæˆ– schema 文äŧķ。

æ–đæĄˆïžščŋ™å°ąæ˜Ŋæˆ‘äžšå†™įš„æ ·å­ã€‚ åŠįœ‹é—ŪéĒ˜æčŋ°æ—ķïžŒæˆ‘įš„æ–đæĄˆå°ąæ˜Ŋ「åœĻ样朎构造åĪ„å›žé€€åˆ°åž‚åļļįąŧ名äļ”äŧ…æ­Īäļ€åĪ„ã€â€”â€” äļŽæœŽ PR åیå…Ļäļ€č‡īã€‚čŒƒå›ī最小化äļ€äļŠčĄĻčūū垏、äļ€äļŠčĄŒäļšã€äļ€äļŠé’‰ä―æĩ‹čŊ•æēĄæœ‰éĄšæ‰‹æ”đåŠĻ或栞垏化噊åĢ°ïžŒäđŸæēĄæœ‰čŊ•å›ū把į›ļé‚ŧįš„åŧķ后éĄđäļ€čĩ·åĄžčŋ›æĨR2-3 įš„æ ·æœŽ/čŪĄæ•°åĢåū„äļäļ€č‡īå°ąåœĻäļ‰čĄŒäđ‹åĪ–ïžŒčĒŦæ­ĢįĄŪåœ°į•™į€æēĄåŠĻ。äļĪæĄéžé˜ŧåĄžįš„å°æƒģæģ•æ”ūåœĻäļ‹éĒįš„äŧĢ᠁åŪĄæŸĨ里。

éĢŽé™Đïžšæ— å‡įš§éĢŽé™ĐäŋĄå·ã€‚ äļĪäļŠæ–‡äŧķéƒ―äļåŒđ配äļŽ revert į›ļå…ģįš„éŦ˜éĢŽé™Đč·Ŋåū„因æ­Īé™Īåļļ规åŪĄæŸĨåĪ–äļéœ€č́éĒåĪ–įš„åŪĄæŸĨæ·ąåšĶ。

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

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

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

@qwen-code-review-bot

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

Copy link
Copy Markdown
Collaborator

Code review

No Critical findings, and no AGENTS.md violations. The change is correct and it stays inside its own scope. Details worth recording, since a green suite on a 9-line diff proves very little on its own:

The fix is right, and it changes nothing else. The ternary's true branch is byte-for-byte the old expression, so a failure that does carry a message renders exactly as before — the counting (additionalFailures), the 3-entry bound, the WARN severity and the throwable-free payload are all untouched. That is what keeps this to item 4 and leaves item R2-3 (the sample records the first failure while the count excludes it) alone, three lines away, as intended. Using getClass().getName() rather than getSimpleName() is the better call for a log line that has to identify a cause across packages.

No new null surface. error.getCause() is now assigned to a local before being dereferenced, but the pre-existing line already dereferenced it in the same expression, and FutureTask always attaches a cause to the ExecutionException it throws — so the change adds no NPE path that was not already there.

The new test is deterministic — I checked this specifically, because it looks racy and it isn't. The production scanner pool is scanner-threads: 4, so a test that splits behaviour on claims.incrementAndGet() == 1 while asserting against join order would be a coin flip. It isn't one here: the 5-argument package-private constructor the test uses (new AutomationScanner(downAtClaim, connector, settings, clock::get, "scanner-silent-store")) defaults the executor to Runnable::run, so tasks execute serially during submission, in findArmed order — which is also the LinkedHashMap join order. Claim order and join order coincide by construction. Worth stating because the design doc already records the opposite case for aTickJoinsEveryRowBeforeItReportsOneFailure, which needs a real one-thread pool precisely because a direct executor cannot pin a join.

The test is load-bearing, not decorative. Against the unpatched construction the sample holds failure=null, so contains("failure=" + IllegalStateException.class.getName()) fails; the doesNotContain("failure=null") assertion pins the old rendering from the other side. No new imports are needed — every symbol it uses is already imported by commonClaimFailuresProduceOneBoundedSummary fifty lines above. logged.list having size 1 is safe: the test calls tick() directly, bypassing scan()'s own catch-and-warn.

Three non-blocking notes — none of them a reason to hold the PR:

  • An empty message still loses the identity. getMessage() != null covers the null case item 4 describes, but an exception carrying "" renders failure= with nothing after it — the same information loss, one edge over. This module declares org.apache.commons.lang3.StringUtils for null-safe blank checks (41 files under src/main use it, none in the automation package yet), and StringUtils.defaultIfBlank(cause.getMessage(), cause.getClass().getName()) would cover both. Equally, Objects.requireNonNullElse(...) is the JDK one-call form of the exact semantics you have now, if you would rather not widen the blank case. Your call — the current form does fix the item as written.
  • The "both halves" claim is slightly stronger than the test. The PR description says the test pins that message-carrying failures keep their rendering. It pins the new half, and it pins the first failure's message via hasMessage, but it never asserts that the sample still reads failure=claim store down — and no other test does either (commonClaimFailuresProduceOneBoundedSummary asserts tenant/schedule substrings and the counts, not the failure text). One extra contains("failure=claim store down") would make that half explicit and cost a line.
  • The new pinning test is not in the design doc's Validation register. That register lists pinning tests by name in both language versions (commonClaimFailuresProduceOneBoundedSummary sits at en:121 / zh-CN:103). Leaving it out is consistent with your stated scope, but it is the same gap fix(managed-agent): close the deferred scanner summary-diagnostics suggestions from #15632 #15700 item 8 tracks for refactor(managed-agent): close the scanner row-pool residues (#15527) #15632's test, so this PR opens a second instance of it. Either add one line per language here, or note on item 8 that it should absorb this test too, so the register does not silently drift.

The three-line comment in tick() is justified — "the sample is the only record of an additional failure" is exactly the kind of non-obvious why that warrants a comment, and it matches the dense why-comments this method already carries.

Test evidence

This is an unattended CI run, so per the review rules I did not build, run, or execute anything from this PR — no mvn, no checkout, no PR-derived code. The evidence below is the PR's own CI, read through the API for the reviewed commit, plus the static reasoning above.

Snapshot at review time — 38 check-runs on f8d70f2, still in flight. The legs that actually validate this diff had not finished, so I am reporting them as pending rather than guessing an outcome:

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

Check Conclusion
(needs.scope.outputs.tier == 'full' || needs.scope.outputs.touched != 'true' || (github.event_name != 'pull_request' && ðŸšŦ cancelled
Build and verify reference distribution ðŸšŦ cancelled
Installed npm global and npx (${{ matrix.target }}) ðŸšŦ cancelled
Installed npm Node26 private Bun acceptance ðŸšŦ cancelled
Publish experimental reference distribution ðŸšŦ cancelled
SDK Java (fast) / ubuntu-latest / Java 21 ðŸšŦ cancelled
Check TUI impact ✅ success
Classify PR ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
SDK Java (fast) / Flyway migration version uniqueness ✅ success
SDK Java (fast) / Real daemon E2E / Java 11 ✅ success
Select the Managed Agent tier ✅ 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åĪąčīĨéĄđ排åœĻ最前。

The one red check is runner infrastructure, not this PR. web-shell E2E Smoke failed in a setup step before any test ran:

cache_dir="${HOME}/.cache/qwen-code/npm"
mkdir -p "${cache_dir}"
â€Ķ
mkdir: Permission denied
##[error]Process completed with exit code 1.

The step immediately before it succeeded (Using pre-installed Node v22.23.4 / npm 10.9.9), so the runner had a working toolchain and then could not create its npm cache directory. I am classifying this from the identity of the failing step and the scope of the diff — a Java-only change under packages/managed-agent-server cannot affect a self-hosted runner's filesystem permissions on ${HOME}/.cache. It needs a maintainer's eye on the runner, not a change to this PR.

Not verified, and explicitly so: the Java leg that runs this module's unit/H2 suite, Checkstyle and SpotBugs (SDK Java (fast) / ubuntu-latest / Java 21) was still in progress at review time, so the new test's actual result and the Checkstyle claim are unconfirmed here. The PR description reports both green locally on macOS with OpenJDK 21 — that is the author's claim, not evidence I re-ran, and I did not treat it as a substitute for the pending check. Per the review rules I do not poll CI; the finalize pass rewrites the table above once these settle.

Sandboxed verification would settle one specific thing: @qwen-code /verify — that aMessageLessRowFailureKeepsItsIdentityInTheSummary genuinely fails against the unpatched construction. The author says they reverted the hunk and watched it fail, which is the right check, but it is a claim I cannot re-run from a static review, and CI only proves the test passes, never that it is load-bearing. Everything else here is settled by the pending Java leg; there is no TUI surface, so /tmux has nothing to drive.

äļ­æ–‡čŊŽ

äŧĢ᠁åŪĄæŸĨ

无 Critical é—ŪéĒ˜ïžŒäđŸæēĄæœ‰čŋå AGENTS.md。æ”đåŠĻæ˜Ŋæ­ĢįĄŪįš„ïžŒåđķäļ”äļĨæ žåŪˆä―č‡Šå·ąįš„čŒƒå›ī。äŧĨäļ‹å‡ į‚đ倞åū—čŪ°å―•ïžŒå› äļš 9 行 diff äļŠįš„įŧŋč‰ēæĩ‹čŊ•朎čšŦčŊŽäļäš†åĪŠåĪšïžš

äŋŪåĪæœŽčšŦæ­ĢįĄŪäļ”æēĄæœ‰éĄšåļĶæ”đ变åˆŦįš„čĄŒäļšã€‚ äļ‰å…ƒčĄĻčūūåžįš„įœŸåˆ†æ”ŊäļŽæ—§čĄĻčūū垏逐字äļ€č‡ī所äŧĨåļĶ message įš„åĪąčīĨæļ翟“åیå…Ļäļå˜ —— čŪĄæ•°ïžˆadditionalFailures、3 æĄäļŠé™ã€WARN įš§åˆŦ、äļæšåļĶ throwable įš„čīŸč――éƒ―æœŠåŠĻ。čŋ™æ­Ģæ˜ŊåŪƒåŠč§Ģå†ģįŽŽ 4 éĄđã€č€ŒæŠŠ R2-3样朎包åŦéĶ–äļŠåĪąčīĨ而čŪĄæ•°äļåŦïž‰åŽŸæ ·į•™åœĻäļ‰čĄŒäđ‹åĪ–įš„åŽŸå› ã€‚į”Ļ getClass().getName() 而非 getSimpleName() åŊđäĄéœ€č́č·Ļ包åŪšä― cause įš„æ—Ĩåŋ—æĨčŊŊæ›īåĨ―įš„é€‰æ‹Đ。

æēĄæœ‰åž•å…Ĩæ–°įš„įĐšæŒ‡é’ˆéĒ。 error.getCause() įŽ°åœĻ先čĩ‹įŧ™åą€éƒĻ变量再č§Ģåž•į”ĻïžŒä―†åŽŸæĨé‚Ģäļ€čĄŒæœŽå°ąåœĻ同äļ€čĄĻčūū垏里č§Ģåž•į”ĻåŪƒïžŒäļ” FutureTask æŠ›å‡šįš„ ExecutionException äļ€åۚåļĶ cause —— 所äŧĨæēĄæœ‰æ–°åĒžåŽŸæœŽäļå­˜åœĻįš„ NPE č·Ŋåū„。

新æĩ‹čŊ•æ˜ŊįĄŪåŪšæ€§įš„ —— 我į‰đ意æŸĨ乆čŋ™į‚đ因äļšåŪƒįœ‹čĩ·æĨ像įŦžæ€ïžŒåŪžé™…äļæ˜Ŋ。 į”Ÿäš§æ‰Ŧ描å™ĻįšŋįĻ‹æą æ˜Ŋ scanner-threads: 4因æ­Īäļ€äļŠį”Ļ claims.incrementAndGet() == 1 åŒšåˆ†čĄŒäļšã€åˆæŒ‰ join éĄšåšæ–­čĻ€įš„æĩ‹čŊ•朎čŊĨæ˜ŊæŽ·įĄŽåļã€‚čŋ™é‡Œäļæ˜Ŋïžšæĩ‹čŊ•ä―ŋį”Ļįš„ 5 å‚åŒ…įš§æž„é€ å‡―æ•°ïžˆnew AutomationScanner(downAtClaim, connector, settings, clock::get, "scanner-silent-store")把 executor éŧ˜čŪĪæˆ Runnable::runäŧŧåŠĄåœĻ提äšĪčŋ‡įĻ‹äļ­äļēčĄŒæ‰§čĄŒïžŒéĄšåšåģ findArmed éĄšåšïžŒäđŸå°ąæ˜Ŋ LinkedHashMap įš„ join éĄšåšã€‚claim éĄšåšäļŽ join éĄšåšį”ąæž„é€ åĪĐį„ķäļ€č‡ī。äđ‹æ‰€äŧĨč́čŊŽïžščŪūčŪĄæ–‡æĄĢ里恰åĨ―čŪ°å―•čŋ‡į›ļåįš„äū‹å­ —— aTickJoinsEveryRowBeforeItReportsOneFailure åŋ…éĄŧį”ĻᜟåŪžįš„å•įšŋįĻ‹æą ïžŒå› äļšį›īæŽĨ executor 钉äļä― join。

čŋ™äļŠæĩ‹čŊ•æ˜Ŋæœ‰æ•ˆįš„ïžŒäļæ˜Ŋ摆čŪū。 åœĻ朊äŋŪåĪįš„æž„é€ ä ·æœŽæ˜Ŋ failure=nullcontains("failure=" + IllegalStateException.class.getName()) 侚åĪąčīĨïž›doesNotContain("failure=null") äŧŽåĶäļ€äū§é’‰ä―æ—§æļ翟“。无需新åĒž import —— åۃį”Ļåˆ°įš„æŊäļŠįŽĶ号ä–đäš”åčĄŒįš„ commonClaimFailuresProduceOneBoundedSummary å·ēįŧåž•å…Ĩ。logged.list 断čĻ€äļš 1 æĄæ˜ŊåŪ‰å…Ļįš„ïžšæĩ‹čŊ•į›īæŽĨ调į”Ļ tick()įŧ•čŋ‡äš† scan() č‡Šå·ąįš„ catch-and-warn。

äĄéžé˜ŧåĄžæ„č§ —— éƒ―äļæ˜ŊåŽ‹ä―čŋ™äļŠ PR įš„į†į”ąïžš

  • įĐš message äŧäžšäļǿމčšŦäŧ―。 getMessage() != null čĶ†į›–äš†įŽŽ 4 éĄđ描čŋ°įš„ null 情å―ĒïžŒä―†æšåļĶ "" įš„åž‚åļļ侚æļ翟“成 failure=后éĒäŧ€äđˆéƒ―æēĄæœ‰ —— å·Ūäļ€äļŠčūđį•ŒïžŒäŋĄæŊäļĒåĪąæ˜Ŋ同äļ€įąŧ。朎æĻĄå—åĢ°æ˜Žäš† org.apache.commons.lang3.StringUtils į”Ļ䚎įĐšå€žåŪ‰å…Ļįš„ blank æĢ€æŸĨsrc/main äļ‹ 41 äļŠæ–‡äŧķåœĻį”Ļautomation 包į›Ū前čŋ˜æēĄæœ‰ïž‰ïžŒStringUtils.defaultIfBlank(cause.getMessage(), cause.getClass().getName()) čƒ―åŒæ—ķč͆ᛖäļĪį§æƒ…å†ĩ。åĶ‚æžœäļæƒģæ‰ĐåΧ到 blank 情å―ĒObjects.requireNonNullElse(...) 则æ˜Ŋå―“å‰čŊ­äđ‰įš„ JDK å•æŽĄč°ƒį”Ļ写æģ•ã€‚ä― æĨåۚ —— įŽ°æœ‰å†™æģ•įĄŪåŪžæŒ‰å­—éĒäŋŪåĨ―乆čŋ™äļ€éĄđ。
  • 「同æ—ķé’‰ä―äļĪåŠã€įš„čŊīæģ•æŊ”æĩ‹čŊ•朎čšŦį•Ĩ垚。 PR 描čŋ°į§°æĩ‹čŊ•é’‰ä―äš†åļĶ message åĪąčīĨįš„æļ翟“äļå˜ã€‚åŪƒé’‰ä―äš†æ–°įš„äļ€åŠïžŒäđŸé€ščŋ‡ hasMessage é’‰ä―äš†éĶ–äļŠåĪąčīĨįš„ messageïžŒä―†äŧŽæœŠæ–­čĻ€æ ·æœŽé‡Œäŧį„ķæ˜Ŋ failure=claim store down —— å…ķåۃæĩ‹čŊ•äđŸæēĄæœ‰ïžˆcommonClaimFailuresProduceOneBoundedSummary 断čĻ€įš„æ˜Ŋ tenant/schedule 子äļē和čŪĄæ•°ïžŒäļæ˜Ŋ failure 文朎。åΚ加äļ€čĄŒ contains("failure=claim store down") å°ąčƒ―æŠŠčŋ™åŠčŊīæļ…æĨšã€‚
  • æ–°įš„é’‰ä―æĩ‹čŊ•æēĄæœ‰čŋ›čŪūčŪĄæ–‡æĄĢįš„ Validation register。 čŊĨ register åœĻäļĪäļŠčŊ­čĻ€į‰ˆæœŽé‡Œéƒ―æŒ‰åå­—åˆ—å‡šé’‰ä―æĩ‹čŊ•commonClaimFailuresProduceOneBoundedSummary åœĻ en:121 / zh-CN:103。äļåŠ äļŽä― åĢ°æ˜Žįš„čŒƒå›īäļ€č‡īïžŒä―†čŋ™äļŽ fix(managed-agent): close the deferred scanner summary-diagnostics suggestions from #15632 #15700 įŽŽ 8 éĄđ所čŋ―čļŠįš„、针åŊđ refactor(managed-agent): close the scanner row-pool residues (#15527) #15632 æĩ‹čŊ•įš„įžšåĢæ˜Ŋ同äļ€äļŠïžŒäšŽæ˜Ŋ朎 PR åˆåž€äš†įŽŽäšŒäū‹ã€‚č́äđˆåœĻčŋ™é‡ŒæŊį§čŊ­čĻ€å„åŠ äļ€čĄŒïžŒč́äđˆåœĻįŽŽ 8 éĄđ里æģĻæ˜ŽåۃäđŸåš”åļæ”ķčŋ™äļŠæĩ‹čŊ•äŧĨ免 register æ‚„æ‚„æž‚į§ŧ。

tick() 里é‚Ģäļ‰čĄŒæģĻ释æ˜Ŋåˆį†įš„ ——「样朎æ˜ŊéĒåĪ–åĪąčīĨå”Ŋäļ€įš„čŪ°å―•ã€æ­Ģæ˜Ŋé‚Ģį§å€žåū—写äĨįš„éžæ˜ūč€Œæ˜“č§åŽŸå› ïžŒäđŸäļŽčŊĨæ–đæģ•å·ēæœ‰įš„åŊ†é›† why æģĻ释éĢŽæ žäļ€č‡ī。

æĩ‹čŊ•čŊæŪ

čŋ™æ˜Ŋ无䚚倞åŪˆįš„ CI čŋčĄŒïžŒå› æ­Ī按åŪĄæŸĨč§„åˆ™æˆ‘æēĄæœ‰æž„åŧšã€čŋčĄŒæˆ–æ‰§čĄŒæœŽ PR įš„äŧŧä―•å†…åŪđ —— æēĄæœ‰ mvn、æēĄæœ‰ checkout、æēĄæœ‰æ‰§čĄŒ PR åļĶæĨįš„äŧĢį ã€‚äļ‹éĒįš„čŊæŪæ˜Ŋ通čŋ‡ API čŊŧåˆ°įš„ã€čŊĨ PR č‡Šå·ąåœĻčĒŦåŪĄ commit äļŠįš„ CI įŧ“果加äļŠä–‡įš„静态æŽĻį†ã€‚

åŪĄæŸĨæ—ķį‚đįš„åŋŦį…§ —— f8d70f2 äļŠ 38 äļŠ check-runäŧåœĻčŋ›čĄŒäļ­ã€‚įœŸæ­Ģ驌čŊæœŽ diff įš„å‡ äļŠ leg čŋ˜æēĄč·‘åŪŒïžŒæ‰€äŧĨ我åĶ‚åŪžæŠĨ告äļš pendingïžŒč€Œäļæ˜Ŋጜæĩ‹įŧ“果čĄĻæ žč§č‹ąæ–‡éƒĻ分æ­ĪåĪ„äļé‡åĪïž‰ã€‚

å”Ŋäļ€įš„įšĒ check æ˜Ŋ runner åŸšįĄ€čŪūæ–―é—ŪéĒ˜ïžŒäļŽæœŽ PR 无å…ģ。 web-shell E2E Smoke åœĻäŧŧä―•æĩ‹čŊ•čŋčĄŒäđ‹å‰įš„准å·æ­ĨéŠĪå°ąåĪąčīĨ䚆mkdir -p "${HOME}/.cache/qwen-code/npm" æŠĨ mkdir: Permission deniedčŋ›įĻ‹äŧĨ exit code 1 įŧ“束。åŪƒå‰ä­Ĩæ˜ŊæˆåŠŸįš„ïžˆUsing pre-installed Node v22.23.4 / npm 10.9.9čŊŽ runner å·Ĩ具é“ūæ­Ģåļļ及æ˜Ŋ无æģ•创åŧšč‡Šå·ąįš„ npm įž“å­˜į›Ūå―•ã€‚æˆ‘čŋ™äļŠåˆĪ断äūæŪįš„æ˜ŊåĪąčīĨæ­ĨéŠĪįš„čšŦäŧ―和 diff įš„čŒƒå›ī —— äļ€äļŠåŠæ”đ packages/managed-agent-server äļ‹ Java äŧĢį įš„ PRäļåŊčƒ―å―ąå“č‡Šæ‰˜įŪĄ runner äļŠ ${HOME}/.cache įš„æ–‡äŧķįģŧįŧŸæƒé™ã€‚čŋ™éœ€č́įŧĪ者å…ģæģĻ runnerïžŒč€Œäļæ˜Ŋæ”đčŋ™äļŠ PR。

朊銌čŊïžŒåđķæ˜ŽįĄŪčŊŽïžščŋčĄŒæœŽæĻĄå— unit/H2 åĨ—äŧķ、Checkstyle äļŽ SpotBugs įš„ Java legSDK Java (fast) / ubuntu-latest / Java 21åœĻåŪĄæŸĨæ—ķäŧåœĻčŋ›čĄŒäļ­ïžŒæ‰€äŧĨ新æĩ‹čŊ•įš„åŪžé™…įŧ“果和 Checkstyle įŧ“čŪšåœĻæ­Ī朊čĒŦįĄŪčŪĪ。PR 描čŋ°į§°åœĻ macOS + OpenJDK 21 äœŽåœ°äļĪéĄđ均通čŋ‡ —— é‚Ģæ˜Ŋä―œč€…įš„åĢ°æ˜ŽïžŒäļæ˜Ŋ我åĪč·‘įš„čŊæŪ我äđŸæēĄæœ‰æ‹ŋåۃæ›ŋäŧĢčŋ™äļŠ pending įš„ check。按åŪĄæŸĨč§„åˆ™æˆ‘äļč―ŪčŊĒ CIïž›čŋ™äš›č·‘åŪŒåŽïžŒfinalize įŽŊčŠ‚äžšå°ąåœ°é‡å†™äļŠéĒįš„čĄĻ栞。

æē™įŪąéŠŒčŊåŊäŧĨįĄŪåۚäļ€äŧķå…·ä―“įš„äš‹ïžš@qwen-code /verify —— åģ aMessageLessRowFailureKeepsItsIdentityInTheSummary åœĻ朊äŋŪåĪįš„æž„é€ äļ‹įĄŪåŪžäžšåĪąčīĨã€‚ä―œč€…čŊī回退čŊĨ hunk åŽįœ‹į€åۃåĪąčīĨ䚆čŋ™æ˜Ŋæ­ĢįĄŪįš„æĢ€æŸĨæ–đåžïžŒä―†é™æ€åŪĄæŸĨ无æģ•åĪč·‘ïž›CI åŠčƒ―čŊæ˜Žæĩ‹čŊ•通čŋ‡ïžŒæ°ļčŋœčŊæ˜Žäļäš†åۃæ˜Ŋæœ‰æ•ˆįš„ã€‚å…ķä―™éƒĻåˆ†į”ą pending įš„ Java leg å†ģåŪšïž›æēĄæœ‰ TUI éĒ所äŧĨ /tmux 无åŊéĐąåŠĻ。

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

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

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean across every stage; the three notes above are polish, not doubt about the change.

Stepping back: this is what a well-behaved follow-up looks like. A nine-item deferred list was recorded in an open issue instead of being folded into #15632 while that PR was still under review; one item — the only one that lost information rather than reformatting it — got picked up on its own, with the pinning test the issue asked for, and the other eight were explicitly left alone. That is the "don't let review rounds balloon the PR" rule being followed rather than quoted at.

My independent proposal, written from the problem statement before I read the diff, was to fall back to the exception class name at the sample-construction site and nowhere else. The PR does exactly that. I did not find a simpler path it missed, and I looked for one — the ternary is the whole change, and the alternatives I can name (Objects.requireNonNullElse, StringUtils.defaultIfBlank) are the same size with slightly different edge behaviour, not simplifications.

The part I would normally worry about on a diff this small is a test that passes for the wrong reason, so I went after it directly. Two things I expected to be problems turned out not to be: the claims.incrementAndGet() == 1 split is not a race, because the constructor the test uses defaults to a direct executor and so claim order and join order coincide by construction; and the assertion is genuinely load-bearing, because the unpatched expression renders failure=null and the contains on the class name cannot pass against it. Six months from now this reads as an obvious line with a test that explains itself — I would thank the author, not curse them.

What I am not confident about is the one thing I could not check: the Java leg that runs this module's unit/H2 suite, Checkstyle and SpotBugs was still in flight at review time, so the new test's real result is unconfirmed. I am not going to approve against a result that does not exist yet, and I am not going to treat the author's local macOS run as a substitute for it — hence the deferral below rather than an approval now. The red web-shell E2E Smoke check does not change that calculus: it died in a runner cache-setup step with mkdir: Permission denied before running any test, and a Java-only diff cannot cause it. It does deserve a maintainer's look at that runner, separately from this PR.

So: approve on the merits, deferred until CI lands green on the reviewed commit. The three notes — the empty-message edge, the one missing assertion for the unchanged rendering, and the absent Validation-register entry that duplicates #15700 item 8's gap — are all fine to handle here or in the item 8 follow-up. None of them should hold this up.

äļ­æ–‡čŊŽ

äŋĄåŋƒåšĶïžš4/5 —— 各é˜ķæŪĩéƒ―åđē净äļŠéĒäĄæ„č§åąžäšŽæ‰“įĢĻïžŒč€Œäļæ˜ŊåŊđæ”đåŠĻ朎čšŦįš„æ€€į–‘ã€‚

退ä­Ĩįœ‹æ•īä―“ïžščŋ™æ˜Ŋäļ€äļŠč§„čŒƒįš„åŽįŧ­ PR čŊĨæœ‰įš„æ ·å­ã€‚äđéĄđåŧķ后æļ…单čĒŦčŪ°å―•åœĻäļ€äļŠ open issue é‡ŒïžŒč€Œäļæ˜ŊåœĻ #15632 čŋ˜åœĻčŊ„åŪĄæ—ķåĄžčŋ›é‚ĢäļŠ PRïž›éšåŽå•į‹Žå–å‡šäļ€éĄđ —— å…ķäļ­å”Ŋäļ€ã€ŒäļĒäŋĄæŊã€č€Œéžã€ŒæĒäļŠæ žåžã€įš„äļ€éĄđ —— åđķæŒ‰čŊĨ issue įš„čĶæą‚é…äļŠé’‰ä―æĩ‹čŊ•å…ķä―™å…ŦéĄđ明įĄŪäŋæŒäļåŠĻ。čŋ™æ˜ŊåœĻᜟæ­Ģæ‰§čĄŒã€Œäļč́čŪĐčŊ„åŪĄč―ŪæŽĄæŠŠ PR 撑åĪ§ã€čŋ™æĄč§„åˆ™ïžŒč€ŒäļåŠæ˜Ŋåž•į”ĻåŪƒã€‚

åœĻčŊŧ diff äđ‹å‰ïžŒæˆ‘æ đæŪé—ŪéĒ˜æčŋ°å†™äļ‹įš„į‹ŽįŦ‹æ–đæĄˆå°ąæ˜ŊïžšåœĻ样朎构造åĪ„å›žé€€åˆ°åž‚åļļįąŧ名äļ”äŧ…æ­Īäļ€åĪ„ã€‚æœŽ PR æ­Ģæ˜ŊåĶ‚æ­Ī。我æēĄæœ‰æ‰ū到åŪƒæžæŽ‰įš„æ›īįŪ€č·Ŋåū„ïžŒč€Œä˜ŊčŪĪᜟæ‰ūčŋ‡įš„ —— äļ‰å…ƒčĄĻčūūåžå°ąæ˜Ŋå…ĻéƒĻæ”đåŠĻïžŒæˆ‘čƒ―æƒģåˆ°įš„æ›ŋäŧĢ写æģ•Objects.requireNonNullElse、StringUtils.defaultIfBlankïž‰ä―“é‡į›ļ同、及æ˜Ŋčūđį•ŒčĄŒäļšį•Ĩ有å·Ū垂åđķäļæž„成įŪ€åŒ–ã€‚

åŊđčŋ™äđˆå°įš„ diff我通åļļäžšæ‹…åŋƒįš„é—Ūéǘæ˜Ŋ「æĩ‹čŊ•å› é”™čŊŊįš„åŽŸå› é€ščŋ‡ã€ïžŒæ‰€äŧĨ我į›īæŽĨåŽŧæŸĨ乆čŋ™äļ€į‚đ。äļĪäļŠæˆ‘éĒ„æœŸäžšæˆäļšé—ŪéĒ˜įš„åœ°æ–đ最įŧˆéƒ―äļæ˜Ŋïžšclaims.incrementAndGet() == 1 įš„åˆ†æ”Ŋäļæ˜ŊįŦžæ€ïžŒå› äļšæĩ‹čŊ•į”Ļįš„æž„é€ å‡―æ•°éŧ˜čŪĪčĩ°į›īæŽĨ executorclaim éĄšåšäļŽ join éĄšåšį”ąæž„é€ åĪĐį„ķäļ€č‡ī断čĻ€äđŸįĄŪåŪžæœ‰æ•ˆïžŒå› äļšæœŠäŋŪåĪįš„čĄĻčūū垏æļ翟“凚 failure=null针åŊđįąŧåįš„ contains äļåŊčƒ―é€ščŋ‡ã€‚å…­äļŠæœˆåŽå†čŊŧčŋ™æ˜Ŋäļ€čĄŒæ˜ūč€Œæ˜“č§įš„äŧĢį é…äļ€äļŠčƒ―č‡Šæˆ‘čŊŽįš„æĩ‹čŊ• —— 我䞚感č°Ēä―œč€…ïžŒč€Œäļæ˜Ŋ驂äŧ–。

我äļįĄŪåŪšįš„æ˜Ŋå”Ŋäļ€äļ€äŧķæŸĨäļäš†įš„䚋čŋčĄŒæœŽæĻĄå— unit/H2 åĨ—äŧķ、Checkstyle äļŽ SpotBugs įš„ Java leg åœĻåŪĄæŸĨæ—ķäŧåœĻčŋ›čĄŒïžŒæ‰€äŧĨ新æĩ‹čŊ•įš„įœŸåŪžįŧ“果尚朊įĄŪčŪĪ。我äļäžšåŊđį€äļ€äļŠčŋ˜äļå­˜åœĻįš„įŧ“æžœåŽŧ approveäđŸäļäžšæ‹ŋä―œč€…æœŽåœ°įš„ macOS čŋčĄŒæĨæ›ŋäŧĢåۃ —— 因æ­Īäļ‹éĒæ˜ŊåŧķåŽč€ŒéžįŦ‹åģæ‰đ准。įšĒč‰ēįš„ web-shell E2E Smoke äļæ”đ变čŋ™äļŠåˆĪæ–­ïžšåۃåœĻčŋčĄŒäŧŧä―•æĩ‹čŊ•äđ‹å‰ïžŒå°ąåœĻ runner įš„įž“å­˜å‡†å·æ­ĨéŠĪ里äŧĨ mkdir: Permission denied åĪąčīĨäš†ïžŒč€ŒåŠæ”đ Java įš„ diff äļåŊčƒ―åŊžč‡īåŪƒã€‚é‚ĢäļŠ runner įĄŪåŪžå€žåū—įŧĪč€…å•į‹Žįœ‹äļ€įœžïžŒä―†äļŽæœŽ PR 无å…ģ。

įŧ“čŪšïžšå°ąæ”đåŠĻ朎čšŦ而čĻ€æ‰đå‡†ïžŒä―†åŧķ后到 CI åœĻčĒŦåŪĄ commit äļŠč·‘įŧŋ。äĄæ„č§ —— įĐš message įš„čūđį•Œã€įžšå°‘įš„é‚ĢäĄã€Œæļ翟“äļå˜ã€æ–­čĻ€ã€äŧĨ及重åĪäš† #15700 įŽŽ 8 éĄđįžšåĢįš„ Validation register 朊į™ŧčŪ° —— æ”ūåœĻ朎 PR åĪ„į†æˆ–į•™įŧ™įŽŽ 8 éĄđįš„åŽįŧ­éƒ―åŊäŧĨïžŒéƒ―äļåš”æˆäļšåŽ‹ä―åŪƒįš„į†į”ąã€‚

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

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

Approval deferred until CI lands green on f8d70f27e7538c0445f413cb9f6b9bd39ecf48dd; the finalize pass posts the commit-pinned approval and withholds it if anything lands red or the head moves.

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review was cancelled before a review could be posted. Nothing failed and nothing is retried automatically: the run was cancelled — by an operator, an upstream event, or the job exceeding its execution time limit. If you still want a review of this PR, request one with @qwen-code /review. See workflow logs.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

The original cache-permission failure is historical: job 122259155192 on ecs-qwen-hz4-30 failed at 2026-10-27 14:46:06Z. The current cache directory was created later, at 16:28:48Z.

At 17:24:04–05Z we directly tested all six hz hosts using each actual github-runner account: create a unique temporary directory under the existing npm cache, write/read back a fixed marker, then remove only those probe files. All six passed creation, write/readback and cleanup (exit 0), including hz4. No permissions or shared cache contents were changed by that validation.

This PR already has attempt 2 running, so no duplicate rerun was triggered. Its current smoke job on hz1-3 has passed npm-cache configuration and font preflight and is installing dependencies: https://github.com/QwenLM/qwen-code/actions/runs/40524112310/job/122348773711 . This confirms the cache prerequisite is currently usable; full E2E result remains pending.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Local verification on 2026-10-28 for head f8d70f27e7538c0445f413cb9f6b9bd39ecf48dd. The clean local worktree matches the published head.

The message-less sampled failure now logs failure=java.lang.IllegalStateException; the existing bounded-summary case also passes.

Check Command Result
Summary regression tests and style JAVA_HOME=/opt/homebrew/opt/openjdk@21 mvn -q -Dtest='AutomationScannerTest#aMessageLessRowFailureKeepsItsIdentityInTheSummary+commonClaimFailuresProduceOneBoundedSummary' test checkstyle:check exit 0; both tests passed

Java commands ran in packages/managed-agent-server; formatting ran from the repository root. UI screenshots are not applicable to these changes. This is focused local evidence, not a full-suite pass. Remote Java/acceptance/review runs were cancelled; those checks and formal approval still need completion before merge.

@chiga0 chiga0 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.

Scope: 2 files — AutomationScanner.java (method body) and AutomationScannerTest.java (new test). Standard tier.

Findings

No blocking findings.
Approval blockers: none.

What was checked

Read

  • Full context of the changed ExecutionException catch block (lines 155–210 at head). The failureSample branch is the only site in the file calling error.getCause().getMessage(); a grep for getCause and getMessage found no sibling site with the same pattern.
  • Pre-existing commonClaimFailuresProduceOneBoundedSummary: uses only message-carrying failures throughout, so the new ternary (cause.getMessage() != null ? â€Ķ : cause.getClass().getName()) does not alter its path. Consistent with the PR description.
  • New test aMessageLessRowFailureKeepsItsIdentityInTheSummary: correctly exercises both halves — first failure has a message and is rethrown; second is message-less and lands in failureSample as failure=java.lang.IllegalStateException. Assertions contains("failure=" + IllegalStateException.class.getName()) and doesNotContain("failure=null") together pin the fix without ambiguity.

Pre-existing issue not introduced by this PR: error.getCause() itself is not null-checked before the ternary. If ExecutionException were constructed with a null cause, this would NPE as it did before. The PR does not widen this risk.

Not covered: no local working tree, so rungs 2/3 (local build / mutation probe) were not run. The change is confined to a diagnostic-string branch inside a catch block with no behavioral effect outside LOG.warn, so the coverage gap is not material.

Reviewed with AI assistance.

@qqqys qqqys 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.

Approving at f8d70f27e7538c0445f413cb9f6b9bd39ecf48dd.

I read the production hunk in its full context in tick() rather than from the diff alone, because the interesting questions here are whether the fallback can itself throw and whether anything beside the string moved.

No new null surface. The obvious worry with error.getCause() is a null cause turning a diagnostics path into an NPE. That dereference is not new: the pre-patch line was error.getCause().getMessage(), and failure = error.getCause() a few lines above already assigns the same value, with the rethrow chain below doing failure instanceof Error / instanceof RuntimeException. The new code hoists the identical call into a local and dereferences it exactly as before, so the assumption set is unchanged. It also holds structurally — the only source of ExecutionException here is task.getKey().get() on a FutureTask, whose run() stores a caught Throwable, so the cause is non-null on every path that reaches this catch.

Nothing but the string moved. The ternary's true branch is byte-identical to the old expression, so a failure carrying a message renders exactly as before. additionalFailures++, the failureSample.size() < 3 bound, the WARN severity and the throwable-free payload are all untouched, and the failure assignment that drives the rethrow is untouched. So the summary stays bounded in the sense #15632 established — bounded count of samples — and the only length delta is a class name replacing the literal null. getClass().getName() over getSimpleName() is right for a line whose job is identifying a cause across packages.

The pinning test is deterministic, which is not obvious from reading it. The test throws a message-carrying exception on whichever row claims first and a message-less one on the rest, then asserts the rethrown failure hasMessage("claim store down") — but the rethrown failure is the first one in join order, not execution order. Those two orders coincide only because the 5-arg test constructor (AutomationScanner.java lines 91-95) delegates with Runnable::run at line 94, so executor.execute(task) runs each task inline during the submit loop, in LinkedHashMap insertion order. Execution order therefore equals join order equals findArmed order, and row 1 gets the message-carrying throw while row 2 supplies the sampled message-less one. I confirmed this rather than assuming it, because under the 4-thread automationScannerExecutor the same assertions would be genuinely racy. logged.list having size 1 is safe for the reason the code shows: tick() is called directly, bypassing scan()'s own catch-and-warn, and additionalFailures == 1 > 0 produces exactly one WARN.

The test also fails against the unpatched construction from both directions — contains("failure=java.lang.IllegalStateException") would see null, and doesNotContain("failure=null") would trip — so it pins the change rather than merely accompanying it.

On CI: the red entries are cancellations, not failures, and the lane that validates this diff had already produced its result before being cancelled. In SDK Java (fast) / ubuntu-latest / Java 21 the log shows Running com.alibaba.qwen.code.managedagent.automation.AutomationScannerTest followed by Tests run: 71, Failures: 0, Errors: 0, Skipped: 0, and You have 0 Checkstyle violations. earlier in the same job; the job's step 11 was cancelled afterwards, with steps 15-16 skipped as a consequence. review-pr, the npm/Bun acceptance lanes and the reference-distribution lanes are likewise cancelled and have no path to a nine-line diagnostics change in a Java module. A SDK Java (full) tier run is still pending on this commit; I did not wait on it, and nothing in the completed evidence is attributable to this PR.

One adjacent item I am deliberately not raising as a blocker, since it predates this PR and is already tracked: an exception whose message is the empty string still renders failure= with nothing after it, and the sample records the first failure while the count excludes it. The first is the same rendering as before the change, so it is not a regression, and the second is #15700's item R2-3 three lines away. This PR does what it says it does — item 4 only — and leaves the rest alone.

@yiliang114
yiliang114 merged commit 0b3331b into main Oct 28, 2026
99 of 111 checks passed
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.26.3.

@github-actions

Copy link
Copy Markdown
Contributor

Managed Agent gate failed

Run 40628289562 · commit f8d70f27e753

Failing tests

  • com.alibaba.qwen.code.managedagent.ManagedSessionLifecycleTest.aFailedRenameLogsAndChainsTheRootCause
  • com.alibaba.qwen.code.managedagent.HostedAutomationSpringCrashIT.fg11c2SpringFixtureBootsAndScansOnMySql

Tracking issue

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.

4 participants