From ca70cdfcc0e9eae6e94568342facf42f48bedb7d Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 11 Nov 2026 16:51:01 +0800 Subject: [PATCH] fix(errors): retain secondary failures during explicit redaction (#9931) --- src/domain/explicitSensitiveFailure.test.ts | 43 +++++++++++++++++++++ src/domain/explicitSensitiveFailure.ts | 3 ++ 2 files changed, 46 insertions(+) diff --git a/src/domain/explicitSensitiveFailure.test.ts b/src/domain/explicitSensitiveFailure.test.ts index 2a882c7500..82dc095870 100644 --- a/src/domain/explicitSensitiveFailure.test.ts +++ b/src/domain/explicitSensitiveFailure.test.ts @@ -198,6 +198,49 @@ it("redacts cleanup primaries and mirrors the source error's live status", () => }); }); +it("retains sanitized finalization failures and their independent cleanup status", () => { + const primary = new AnalysisInputError("inspect_web_network_capture", { + userMessage: "declared-marker capture is invalid", + }); + const reason = new Error("cancelled during reply finalization"); + const cancellation = new AnalysisCancelledError( + "inspect_web_network_capture", + { cause: reason, diagnostics: { note: "declared-marker finalization" } }, + ); + const cleanup = new ProviderCleanupError( + "har", + ["/tmp/declared-marker-runtime"], + { reason: "declared-marker runtime close failed" }, + { operation: "inspect_web_network_capture" }, + ); + primary.retainSecondaryFailure(cancellation); + primary.retainSecondaryFailure(cleanup); + + const redacted = redactExplicitFailure(primary, ["declared-marker"]); + const [retainedCancellation, retainedCleanup] = redacted.secondaryFailures; + expect(retainedCancellation).toBeInstanceOf(AnalysisCancelledError); + expect(retainedCancellation?.cause).toBe(reason); + expect(retainedCleanup).toBeInstanceOf(ProviderCleanupError); + expect(retainedCleanup?.cleanupIncomplete).toBe(true); + expect(redacted.cleanupIncomplete).toBe(false); + const projected = projectAnalysisError(redacted); + expect(projected.code).toBe("invalid_request"); + expect(projected.details?.secondary_failures).toMatchObject([ + { + code: "cancelled", + details: { diagnostics: { note: "[REDACTED] finalization" } }, + }, + { + code: "cleanup_incomplete", + details: { resources: ["/tmp/[REDACTED]-runtime"] }, + }, + ]); + expect(JSON.stringify(projected)).not.toContain("declared-marker"); + cleanup.resolveCleanup(); + expect(retainedCleanup?.cleanupIncomplete).toBe(false); + expect(redacted.cleanupIncomplete).toBe(false); +}); + it("keeps a named timeout control through explicit-value redaction", () => { const error = new AnalysisTimeoutError( "analyze_javascript_application", diff --git a/src/domain/explicitSensitiveFailure.ts b/src/domain/explicitSensitiveFailure.ts index 4c7cd25114..fe626b462c 100644 --- a/src/domain/explicitSensitiveFailure.ts +++ b/src/domain/explicitSensitiveFailure.ts @@ -84,6 +84,9 @@ export const redactSensitiveFailure = ( // Other observation owners already apply their format-aware exclusions. if (redacted !== error && error.partialObservation !== undefined) redacted.retainPartialObservation(error.partialObservation); + if (redacted !== error) + for (const secondary of error.secondaryFailures) + redacted.retainSecondaryFailure(redactSensitiveFailure(secondary, text)); return redacted === error ? error : mirrorCleanup(error, redacted, text); };