From b5de0d9ffac5795624d3abaec11a92a8848cab01 Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:45:31 +0100 Subject: [PATCH] fix(terminal): push worktree branch to remote with tracking on creation (#1753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(terminal): push worktree branch to remote with tracking on creation Terminal worktree branches were created with --no-track and never pushed, leaving them in an undefined state with no upstream configured. This caused confusion when subsequently pushing commits from the worktree. Now after git worktree add, we check for an origin remote and run git push -u origin terminal/{name} to establish proper tracking. Push failures are non-fatal — the worktree is still usable but a warning is surfaced to the caller. Co-Authored-By: Claude Opus 4.6 * fix(terminal): surface remote push warning via toast notification The warning field returned by createTerminalWorktree was not consumed by the UI, so users had no visibility when remote tracking failed to set up. Now shows a destructive toast with translated message when the worktree is created but the push to remote fails. Added i18n keys for both en and fr locales. Co-Authored-By: Claude Opus 4.6 * fix(terminal): separate remote check from push and surface actual error Split the single try-catch into two: silently skip push for local-only repos (no origin), only warn when origin exists but push fails. Show the actual error message in the toast instead of a generic string. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Test User Co-authored-by: Claude Opus 4.6 --- .../terminal/worktree-handlers.ts | 44 ++++++++++++++++++- .../terminal/CreateWorktreeDialog.tsx | 10 +++++ .../src/shared/i18n/locales/en/terminal.json | 4 +- .../src/shared/i18n/locales/fr/terminal.json | 4 +- apps/frontend/src/shared/types/terminal.ts | 4 ++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts b/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts index 57d6bf58..f98f40e3 100644 --- a/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts @@ -451,6 +451,9 @@ async function createTerminalWorktree( } } + let remoteTrackingSetUp = false; + let remotePushWarning: string | undefined; + if (createGitBranch) { // Use --no-track to prevent the new branch from inheriting upstream tracking // from the base ref (e.g., origin/main). This ensures users can push with -u @@ -463,6 +466,44 @@ async function createTerminalWorktree( env: getIsolatedGitEnv(), }); debugLog('[TerminalWorktree] Created worktree with branch:', branchName, 'from', baseRef); + + // Push the new branch to remote and set up tracking so subsequent + // git push/pull operations work correctly from the worktree. + // This prevents branches from accumulating local-only commits with + // no upstream configured, which causes confusion when pushing later. + // Check if 'origin' remote exists — silently skip for local-only repos + let hasOrigin = false; + try { + await execFileAsync(getToolPath('git'), ['remote', 'get-url', 'origin'], { + cwd: projectPath, + encoding: 'utf-8', + timeout: 5000, + env: getIsolatedGitEnv(), + }); + hasOrigin = true; + } catch { + // No origin remote — local-only repo, nothing to push to + debugLog('[TerminalWorktree] No origin remote found, skipping push for local-only repo'); + } + + if (hasOrigin) { + try { + await execFileAsync(getToolPath('git'), ['push', '-u', 'origin', branchName], { + cwd: worktreePath, + encoding: 'utf-8', + timeout: 30000, + env: getIsolatedGitEnv(), + }); + remoteTrackingSetUp = true; + debugLog('[TerminalWorktree] Pushed branch to remote with tracking:', branchName); + } catch (pushError) { + // Worktree was created successfully — don't fail the operation, + // but surface a warning so the user knows tracking isn't set up. + const message = pushError instanceof Error ? pushError.message : 'Unknown push error'; + remotePushWarning = message; + debugLog('[TerminalWorktree] Could not push to remote (worktree still usable):', message); + } + } } else { // Use async to avoid blocking the main process on large repos. await execFileAsync(getToolPath('git'), ['worktree', 'add', '--detach', worktreePath, baseRef], { @@ -490,12 +531,13 @@ async function createTerminalWorktree( taskId, createdAt: new Date().toISOString(), terminalId, + remoteTrackingSetUp, }; saveWorktreeConfig(projectPath, name, config); debugLog('[TerminalWorktree] Saved config for worktree:', name); - return { success: true, config }; + return { success: true, config, warning: remotePushWarning }; } catch (error) { debugError('[TerminalWorktree] Error creating worktree:', error); diff --git a/apps/frontend/src/renderer/components/terminal/CreateWorktreeDialog.tsx b/apps/frontend/src/renderer/components/terminal/CreateWorktreeDialog.tsx index 6ef218cf..b3246cb3 100644 --- a/apps/frontend/src/renderer/components/terminal/CreateWorktreeDialog.tsx +++ b/apps/frontend/src/renderer/components/terminal/CreateWorktreeDialog.tsx @@ -21,6 +21,7 @@ import { SelectValue, } from '../ui/select'; import { Combobox } from '../ui/combobox'; +import { useToast } from '../../hooks/use-toast'; import { buildBranchOptions } from '../../lib/branch-utils'; import type { Task, TerminalWorktreeConfig, GitBranchDetail } from '../../../shared/types'; import { useProjectStore } from '../../stores/project-store'; @@ -84,6 +85,7 @@ export function CreateWorktreeDialog({ onWorktreeCreated, }: CreateWorktreeDialogProps) { const { t } = useTranslation(['terminal', 'common']); + const { toast } = useToast(); const [name, setName] = useState(''); const [selectedTaskId, setSelectedTaskId] = useState(); const [createGitBranch, setCreateGitBranch] = useState(true); @@ -225,6 +227,14 @@ export function CreateWorktreeDialog({ setName(''); setSelectedTaskId(undefined); setCreateGitBranch(true); + // Notify user if remote tracking could not be set up + if (result.warning) { + toast({ + title: t('terminal:worktree.remotePushFailed'), + description: result.warning || t('terminal:worktree.remotePushFailedDescription'), + variant: 'destructive', + }); + } } else { setError(result.error || t('common:errors.generic')); } diff --git a/apps/frontend/src/shared/i18n/locales/en/terminal.json b/apps/frontend/src/shared/i18n/locales/en/terminal.json index 318c39c7..93c9d86b 100644 --- a/apps/frontend/src/shared/i18n/locales/en/terminal.json +++ b/apps/frontend/src/shared/i18n/locales/en/terminal.json @@ -41,6 +41,8 @@ "alreadyExists": "A worktree with this name already exists", "deleteTitle": "Delete Worktree?", "deleteDescription": "This will permanently delete the worktree and its branch. Any uncommitted changes will be lost.", - "detached": "(detached)" + "detached": "(detached)", + "remotePushFailed": "Remote Tracking Not Set Up", + "remotePushFailedDescription": "Worktree created but the branch could not be pushed to remote. You may need to run git push -u manually." } } diff --git a/apps/frontend/src/shared/i18n/locales/fr/terminal.json b/apps/frontend/src/shared/i18n/locales/fr/terminal.json index bdee2aa2..742bdb6e 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/terminal.json +++ b/apps/frontend/src/shared/i18n/locales/fr/terminal.json @@ -41,6 +41,8 @@ "alreadyExists": "Un worktree avec ce nom existe deja", "deleteTitle": "Supprimer le Worktree?", "deleteDescription": "Ceci supprimera definitivement le worktree et sa branche. Les modifications non committées seront perdues.", - "detached": "(détaché)" + "detached": "(détaché)", + "remotePushFailed": "Suivi distant non configure", + "remotePushFailedDescription": "Le worktree a ete cree mais la branche n'a pas pu etre poussee vers le depot distant. Vous devrez peut-etre executer git push -u manuellement." } } diff --git a/apps/frontend/src/shared/types/terminal.ts b/apps/frontend/src/shared/types/terminal.ts index ff30f451..968a5409 100644 --- a/apps/frontend/src/shared/types/terminal.ts +++ b/apps/frontend/src/shared/types/terminal.ts @@ -219,6 +219,8 @@ export interface TerminalWorktreeConfig { createdAt: string; /** Terminal ID this worktree is associated with */ terminalId: string; + /** Whether the branch was pushed to remote with tracking set up */ + remoteTrackingSetUp?: boolean; } /** @@ -252,6 +254,8 @@ export interface TerminalWorktreeResult { success: boolean; config?: TerminalWorktreeConfig; error?: string; + /** Warning when worktree was created but remote push failed */ + warning?: string; } /**