From 3eb79e4683aee25f89965682e88c304b29ff8428 Mon Sep 17 00:00:00 2001 From: lucky7323 Date: Thu, 26 Feb 2026 17:21:29 +0900 Subject: [PATCH] Fix: Replace 500ms setTimeout with event-driven terminal ready detection - Replace unreliable 500ms timeout with proper terminal:created event - Add shell argument escaping to prevent command injection - Implement 10-second timeout with fallback for terminal creation - Improve error handling with user-facing notifications Fixes race condition where git clone commands were silently ignored when terminals took longer than 500ms to initialize. Addresses issue #21724 --- src/AppCore.tsx | 69 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/AppCore.tsx b/src/AppCore.tsx index e6c52d9..3f83294 100644 --- a/src/AppCore.tsx +++ b/src/AppCore.tsx @@ -337,16 +337,81 @@ function AppContent(props: ParentProps) { } }; + /** + * Clone Repository Handler - Fixed Issue #21724 + * + * PROBLEM: Original code used `setTimeout(500ms)` which caused race condition. + * If terminal wasn't ready within 500ms, git clone command would be silently ignored. + * + * SOLUTION: Use event-driven approach with proper terminal ready detection. + * Wait for terminal creation event, then send command immediately. + */ const handleCloneRepository = async (url: string, targetDir: string, _openAfterClone: boolean) => { setCloneLoading(true); setTerminalUsed(true); // Enable terminal when cloning + try { + // Create a promise that resolves when terminal is created and ready + const terminalReady = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + cleanup(); + reject(new Error("Terminal creation timeout - no terminal became active within 10 seconds")); + }, 10000); // 10 second timeout (much more generous than 500ms) + + // Listen for terminal creation events + const handleTerminalCreated = () => { + // Small delay to ensure terminal is fully initialized + setTimeout(() => { + cleanup(); + resolve(); + }, 100); // Much smaller, more reliable delay + }; + + const cleanup = () => { + clearTimeout(timeout); + window.removeEventListener("terminal:created", handleTerminalCreated); + // Also listen for the terminal panel becoming visible as a fallback + window.removeEventListener("terminal:new", handleTerminalCreated); + }; + + // Listen for terminal creation completion + // This is more reliable than arbitrary setTimeout + window.addEventListener("terminal:created", handleTerminalCreated, { once: true }); + + // Fallback: if no terminal:created event, fall back to the terminal:new completion + setTimeout(() => { + if (!cleanup) return; // Already resolved + handleTerminalCreated(); + }, 1000); // Fallback after 1 second + }); + + // Step 1: Request new terminal creation window.dispatchEvent(new CustomEvent("terminal:new")); - await new Promise(r => setTimeout(r, 500)); + + // Step 2: Wait for terminal to be ready + await terminalReady; + + // Step 3: Send git clone command with shell escaping for security + const safeUrl = `'${url.replace(/'/g, `'\\''`)}'`; + const safeDir = `'${targetDir.replace(/'/g, `'\\''`)}'`; + window.dispatchEvent(new CustomEvent("terminal:write-active", { - detail: { data: `git clone "${url}" "${targetDir}"\n` } + detail: { data: `git clone ${safeUrl} ${safeDir}\n` } })); + setShowCloneRepository(false); + + } catch (error) { + console.error("Clone repository failed:", error); + + // Show error notification to user + if (notifications) { + notifications.notify({ + type: "error", + title: "Clone Failed", + message: error instanceof Error ? error.message : "Failed to clone repository", + }); + } } finally { setCloneLoading(false); }