diff --git a/extensions/qa-lab/src/tool-search-gateway-request-evidence.ts b/extensions/qa-lab/src/tool-search-gateway-request-evidence.ts index fc6a3ba9a42c..e4b4b3a68148 100644 --- a/extensions/qa-lab/src/tool-search-gateway-request-evidence.ts +++ b/extensions/qa-lab/src/tool-search-gateway-request-evidence.ts @@ -104,10 +104,10 @@ export async function throwToolSearchGatewayRequestFailure(params: { : errorCode === "ETIMEDOUT" || errorCode === "ETOOBIG" ? errorCode : "request failed"; + // Scenario formatting traverses causes; raw HTTP errors can include complete provider bodies. throw new Error( `Tool Search ${params.lane} lane gateway request failed (${safeFailure}); ` + `providerRequests=${JSON.stringify(providerRequests)}; ` + `sessionMentions=${JSON.stringify(sessionMentions)}${safeLogs}`, - { cause: params.cause }, ); } diff --git a/extensions/qa-lab/src/tool-search-gateway.fixture.test.ts b/extensions/qa-lab/src/tool-search-gateway.fixture.test.ts index dbdbcbdcf534..3d4bf9266cc4 100644 --- a/extensions/qa-lab/src/tool-search-gateway.fixture.test.ts +++ b/extensions/qa-lab/src/tool-search-gateway.fixture.test.ts @@ -16,6 +16,7 @@ import { qaMockRequestsAfterUrl, readQaMockRequestCursor, } from "./providers/shared/debug-request-cursor.js"; +import { runQaSuiteScenarioSteps } from "./suite-runtime-flow.js"; import type { QaSuiteRuntimeEnv } from "./suite-runtime-types.js"; import { assertToolSearchBatchLaneResult, @@ -369,7 +370,7 @@ describe("tool search gateway e2e lane result", () => { } }); - it("reports bounded stage evidence for a failed gateway request without leaking diagnostics", async () => { + it("renders bounded stage evidence for a failed gateway request without leaking diagnostics", async () => { const gatewaySecret = "gateway-secret-value"; const responseSecret = "raw-response-secret"; const promptSecret = "raw-prompt-secret"; @@ -416,30 +417,35 @@ describe("tool search gateway e2e lane result", () => { vi.stubGlobal("fetch", fetchMock); try { - const failure = await runToolSearchGatewayLane({ - env, - fixture: { fakePluginDir: tempRoot, targetTool: "fake_plugin_tool_17" }, - lane: "code", - }).catch((error: unknown) => error); + const scenario = await runQaSuiteScenarioSteps("Tool Search failure evidence", [ + { + name: "runs the compact lane", + run: async () => { + await runToolSearchGatewayLane({ + env, + fixture: { fakePluginDir: tempRoot, targetTool: "fake_plugin_tool_17" }, + lane: "code", + }); + }, + }, + ]); - expect(failure).toBeInstanceOf(Error); - const error = failure as Error; - expect(error.message).toContain("Tool Search code lane gateway request failed (HTTP 502)"); - expect(error.message).toContain( + expect(scenario.status).toBe("fail"); + const renderedError = scenario.details ?? ""; + expect(renderedError).toContain("Tool Search code lane gateway request failed (HTTP 502)"); + expect(renderedError).toContain( 'providerRequests=[{"plannedToolName":"tool_search_code","declaredToolCount":1,"targetDeclared":false,"bridgeDeclared":true,"targetResultObserved":true}]', ); - expect(error.message).toContain( + expect(renderedError).toContain( 'sessionMentions={"tool_search_code":1,"tool_search":1,"tool_call":0,"fake_plugin_tool_17":1}', ); - expect(error.message).toContain("Gateway logs:"); - expect(error.message).toContain("gateway-stage-tail"); - expect(error.message).not.toContain(gatewaySecret); - expect(error.message).not.toContain(responseSecret); - expect(error.message).not.toContain(promptSecret); - expect(error.message).not.toContain(toolOutputSecret); - expect(error.message.length).toBeLessThan(6_000); - expect(error.cause).toBeInstanceOf(Error); - expect((error.cause as Error).message).toContain(responseSecret); + expect(renderedError).toContain("Gateway logs:"); + expect(renderedError).toContain("gateway-stage-tail"); + expect(renderedError).not.toContain(gatewaySecret); + expect(renderedError).not.toContain(responseSecret); + expect(renderedError).not.toContain(promptSecret); + expect(renderedError).not.toContain(toolOutputSecret); + expect(renderedError.length).toBeLessThan(6_000); } finally { await fs.rm(tempRoot, { force: true, recursive: true }); }