@@ -2364,6 +2364,55 @@ describe("runAgentTurnWithFallback", () => {
|
2364 | 2364 | } |
2365 | 2365 | }); |
2366 | 2366 | |
| 2367 | +it.each([ |
| 2368 | +{ |
| 2369 | +rejection: new Error("CLI exceeded timeout (300s) and was terminated."), |
| 2370 | +modeLabel: "overall CLI turn budget" as const, |
| 2371 | +routingSubstring: undefined as string | undefined, |
| 2372 | +}, |
| 2373 | +{ |
| 2374 | +rejection: new Error("CLI produced no output for 120s and was terminated."), |
| 2375 | +modeLabel: "no-output stall" as const, |
| 2376 | +routingSubstring: undefined, |
| 2377 | +}, |
| 2378 | +{ |
| 2379 | +rejection: new Error( |
| 2380 | +"All models failed (2): anthropic/claude-opus-4-7: CLI exceeded timeout (300s) and was terminated. | anthropic/foo: bar", |
| 2381 | +), |
| 2382 | +modeLabel: "overall CLI turn budget" as const, |
| 2383 | +routingSubstring: "(routing anthropic/claude-opus-4-7)", |
| 2384 | +}, |
| 2385 | +{ |
| 2386 | +rejection: new Error("codex-cli/gpt-5.5: CLI exceeded timeout (60s) and was terminated."), |
| 2387 | +modeLabel: "overall CLI turn budget" as const, |
| 2388 | +routingSubstring: "(routing codex-cli/gpt-5.5)", |
| 2389 | +}, |
| 2390 | +])( |
| 2391 | +"surfaces CLI subprocess timeout copy instead of generic failure when verbose is off ($modeLabel)", |
| 2392 | +async ({ rejection, modeLabel, routingSubstring }) => { |
| 2393 | +state.runWithModelFallbackMock.mockRejectedValueOnce(rejection); |
| 2394 | + |
| 2395 | +const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); |
| 2396 | +const result = await runAgentTurnWithFallback({ |
| 2397 | + ...createMinimalRunAgentTurnParams(), |
| 2398 | +}); |
| 2399 | + |
| 2400 | +expect(result.kind).toBe("final"); |
| 2401 | +if (result.kind !== "final") { |
| 2402 | +throw new Error("expected final reply"); |
| 2403 | +} |
| 2404 | +expect(result.payload.text).not.toBe(GENERIC_RUN_FAILURE_TEXT); |
| 2405 | +expect(result.payload.text).toContain("CLI subprocess"); |
| 2406 | +expect(result.payload.text).not.toContain("Claude CLI"); |
| 2407 | +expect(result.payload.text).toContain(modeLabel); |
| 2408 | +expect(result.payload.text).toContain("gateway may still be healthy"); |
| 2409 | +expect(result.payload.text).toContain("cliBackends.<your-runtime>"); |
| 2410 | +if (routingSubstring) { |
| 2411 | +expect(result.payload.text).toContain(routingSubstring); |
| 2412 | +} |
| 2413 | +}, |
| 2414 | +); |
| 2415 | + |
2367 | 2416 | it("forwards sanitized generic errors on external chat channels when verbose is on", async () => { |
2368 | 2417 | state.runEmbeddedPiAgentMock.mockRejectedValueOnce( |
2369 | 2418 | new Error("INVALID_ARGUMENT: some other failure"), |
|