unbridled-41 opened a new pull request, #5118:
URL: https://github.com/apache/rocketmq-dashboard/pull/5118

   ### Issue linkage
   
   No existing issue or open PR covers this path. Checked: `gh search 
prs/issues --repo apache/rocketmq-dashboard` for `AiRunExecutor`, 
`decide(RunContext`, `error terminal state` — the only open artifacts touching 
this class are #4999 (terminal-frame duration, mine) and #4740, and neither 
mentions the failure path below; the open issue #5003 is about the replayed 
`run_finished` frame, not about the terminal state.
   
   ### Problem / Evidence
   
   `AiRunExecutor.execute` 
(`server/src/main/java/org/apache/rocketmq/studio/ops/ai/conversation/AiRunExecutor.java:274-294`)
 catches `LlmGatewayException` and `RuntimeException` and then decides the 
terminal state in `finally`. An `Error` matches neither catch, so `decide()` 
(`:481-495`) sees an empty `Outcome` and falls through to its last branch:
   
   ```java
   return new Terminal(RunStatus.COMPLETED, null, null, null, outcome, false);
   ```
   
   Call path: `AiRunService.start` -> `AiRunExecutor.submit` -> worker -> 
`execute(RunContext)` -> `streamWithLostResumeRetry` -> `finally { 
finalizeRun(context, decide(context, outcome)) }` -> run row written in 
`finalizeRun` (`:515-526`).
   
   Observed **before** the fix — the new regression test, run against the 
pre-fix revision of `AiRunExecutor` (`git checkout origin/rocketmq-studio -- 
.../AiRunExecutor.java`):
   
   ```text
   INFO  AiRunExecutor -- agent run 11 finished: status=COMPLETED reason=null 
durationMs=13 ... endSeq=3
   expected: "FAILED"
    but was: "COMPLETED"
   Tests run: 1, Failures: 1, Errors: 0
   ```
   
   So a `StackOverflowError` while streaming — a deeply nested tool payload 
through Jackson is the realistic producer — was written to the run row as a 
completed turn with no answer, the transcript replays it as finished, and 
`AgentRunHandle.stop` has nothing left to correct.
   
   Observed **after** the fix: the same test reports `status=FAILED 
reason=PROVIDER_ERROR`, error code `ai.run.internal_error`, `finishedAt` set, 
emitter completed.
   
   ### Root cause / Fix
   
   `Outcome.unexpected` only accepted a `RuntimeException`, and nothing caught 
an `Error`. Catch `Error` too, record it in the same field (widened to 
`Throwable`), let the existing `finally` write the terminal row, and rethrow so 
the failure still reaches the worker's uncaught handler.
   
   ### Priority / fix confidence
   
   - PRIORITY = **62** — impact 24/40 (a crashed run is presented as a 
completed answer), scope 14/20 (every hosted agent run), reproducibility 16/20 
(deterministic unit test), maintenance value 8/20 (restores the invariant the 
sibling `RuntimeException` branch already implements).
   - FIX_CONFIDENCE = **88** — the change mirrors the catch/record/decide path 
that already exists for `RuntimeException`, and the reasoning is pinned by 
`anUnexpectedProviderFailureShouldNotLeaveTheRunNonTerminalTest`.
   
   ### Tests
   
   | Command | Result |
   |---|---|
   | `mvn -o -q test -Dtest=AiRunExecutorTest` | 19 tests, 0 failures |
   | `mvn -o -q test -Dtest='org.apache.rocketmq.studio.ops.ai.**'` | 961 
tests, 0 failures, 0 errors |
   | `mvn -o test` (whole suite) | Tests run: 3288, Failures: 0, Errors: 17 |
   | `mvn -o checkstyle:check` | clean |
   
   The 17 whole-suite errors are the MySQL-backed `*IntegrationTest` / 
`StudioApplicationTest` Spring context-load failures this environment always 
produces; the errored set is byte-identical to the set measured on the 
untouched base revision `4ee173ad`. One whole-suite run additionally hit 
`ResourceOwnershipGuardTest#migrationConcurrentStartupIsIdempotentTest` 
(`java.util.ConcurrentModificationException` inside H2's 
`JdbcDatabaseMetaData.getIndexInfo`); that test fails 3/3 standalone on the 
untouched base revision here as well, so it is an environment race, not this 
change.
   
   New test: 
`AiRunExecutorTest#anErrorFromTheWorkerShouldNotBeRecordedAsACompletedRunTest`. 
`AiRunTestSupport.StubAgentProvider` gained an `Error error` field next to its 
`RuntimeException failure` so a test can reach paths only an `Error` takes.
   
   ### Risk
   
   A worker that dies with an `Error` now finalises the row as FAILED before 
the error propagates; nothing else reads `Outcome.unexpected`, and the 
`finally` block already ran for every other exception type.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to