From 949cd4efd8f377ebd18f27e719b4e6268b6461f3 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 13 Oct 2025 00:20:23 +0200 Subject: [PATCH] Fix console.log duplication bug in ConsoleRuntimeProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: Each browser_javascript execution wrapped console methods, but captured the current (already wrapped) console as "original". This created a chain of wrappers that accumulated across executions: - Execution 1: 1x console.log (wrapper1 → real console) - Execution 2: 2x console.log (wrapper2 → wrapper1 → real console) - Execution 3: 3x console.log (wrapper3 → wrapper2 → wrapper1 → real console) - Execution 4: 4x console.log (and so on...) Fix: Store the truly original console methods in window.__originalConsole on first wrap only. All subsequent executions use these stored original methods instead of capturing the current console. This prevents wrapper accumulation. Changes: - Check if window.__originalConsole exists before wrapping - Store original console methods with .bind() to preserve context - Always use window.__originalConsole for local logging - Now each execution logs exactly 1x regardless of execution count --- .../sandbox/ConsoleRuntimeProvider.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts b/packages/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts index f5f4be5f..23fa13d2 100644 --- a/packages/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts +++ b/packages/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts @@ -25,13 +25,19 @@ export class ConsoleRuntimeProvider implements SandboxRuntimeProvider { getRuntime(): (sandboxId: string) => void { return (_sandboxId: string) => { - // Console capture with immediate send pattern - const originalConsole = { - log: console.log, - error: console.error, - warn: console.warn, - info: console.info, - }; + // Store truly original console methods on first wrap only + // This prevents accumulation of wrapper functions across multiple executions + if (!(window as any).__originalConsole) { + (window as any).__originalConsole = { + log: console.log.bind(console), + error: console.error.bind(console), + warn: console.warn.bind(console), + info: console.info.bind(console), + }; + } + + // Always use the truly original console, not the current (possibly wrapped) one + const originalConsole = (window as any).__originalConsole; // Track pending send promises to wait for them in onCompleted const pendingSends: Promise[] = []; @@ -48,7 +54,7 @@ export class ConsoleRuntimeProvider implements SandboxRuntimeProvider { }) .join(" "); - // Always log locally too + // Always log locally too (using truly original console) (originalConsole as any)[method].apply(console, args); // Send immediately and track the promise (only in extension context)