fix(terminal): push worktree branch to remote with tracking on creation (#1753)

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-02-06 21:45:31 +01:00
committed by GitHub
parent 445da186c8
commit b5de0d9ffa
5 changed files with 63 additions and 3 deletions
@@ -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);
@@ -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<string | undefined>();
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'));
}
@@ -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."
}
}
@@ -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."
}
}
@@ -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;
}
/**