Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions src/AppCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>((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);
}
Expand Down