From fad5d81aebcc672bb586713bcbe6ce4a9375c020 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Fri, 30 Oct 2026 10:43:51 +0800 Subject: [PATCH] test(managed): record which load path produced each post-crash entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HostedProcessCrashIT fails on main with postFailureLoads.every(Boolean), but the recorded sequence changes shape between runs (#15898: 9 vs 10 entries, different true positions, one run with none), so the current message cannot say which of the connector's load paths sent the offending request. Record a compact origin discriminator per entry — cancellationTakeover, passiveManagedRuntimeRecovery, driveRuntimeRecovery, mcpManagementOnly and whether cancellationPromptId was present — and include it in both assertion messages. The assertion itself is unchanged: 'no tool replay after a crash' is the property it protects, and the next red run should name its own origin instead of being guessed at. --- .../helpers/hosted-process-crash-driver.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/integration-tests/helpers/hosted-process-crash-driver.ts b/integration-tests/helpers/hosted-process-crash-driver.ts index a1b8556833e..ef8a35c27ef 100644 --- a/integration-tests/helpers/hosted-process-crash-driver.ts +++ b/integration-tests/helpers/hosted-process-crash-driver.ts @@ -50,6 +50,11 @@ const report = { promptSubmissions: 0, continuationsAfterFailure: 0, postFailureLoads: [] as boolean[], + // Which caller produced each entry above. The boolean says a post-failure + // load was not cancellation-only; it does not say which of the connector's + // load paths sent it. #15898 shows the sequence changing shape between runs, + // so a failing entry has to name its own origin. + postFailureLoadOrigins: [] as string[], lateCancelReplays: 0, originalCancelBody: '', }; @@ -156,8 +161,18 @@ const proxy = createServer(async (req, res) => { `/session/${config.sessionId}/managed-runtime/continue` ) report.continuationsAfterFailure++; - if (injected && target.pathname.endsWith('/load')) + if (injected && target.pathname.endsWith('/load')) { report.postFailureLoads.push(fields.cancellationTakeover === true); + report.postFailureLoadOrigins.push( + [ + `takeover=${fields.cancellationTakeover === true}`, + `passive=${fields.passiveManagedRuntimeRecovery === true}`, + `drive=${fields.driveRuntimeRecovery === true}`, + `mcpOnly=${fields.mcpManagementOnly === true}`, + `promptId=${typeof fields.cancellationPromptId === 'string'}`, + ].join(' '), + ); + } const headers = Object.fromEntries( Object.entries(req.headers) .filter( @@ -679,10 +694,13 @@ async function runRecoverySettlement() { assert.equal(report.modelCalls, 1); assert.equal(report.promptSubmissions, 1); assert.equal(report.continuationsAfterFailure, 0); - assert(report.postFailureLoads.length > 0); + assert( + report.postFailureLoads.length > 0, + `no post-failure loads were recorded at all: ${JSON.stringify(report.postFailureLoadOrigins)}`, + ); assert( report.postFailureLoads.every(Boolean), - 'replacement must use cancellation-only loads', + `replacement must use cancellation-only loads: ${report.postFailureLoadOrigins.join(' | ')}`, ); await unlink(`${proof}.read-gate`); await waitUntil(async () => {