Show error when remote branch status is unavailable (Vibe Kanban) (#1825)

* I've completed the implementation. Here's a summary of the changes:

## Summary

### Changes Made

1. **`frontend/src/hooks/useBranchStatus.ts`**
   - Modified to return `{ data, error, isError, isLoading, refetch }` instead of just the query result
   - This exposes the error state to consuming components

2. **`frontend/src/components/tasks/Toolbar/GitOperations.tsx`**
   - Added `branchStatusError` prop to interface
   - Removed `hasConflictsCalculated` from the "Change target branch" button's disabled condition - now it stays enabled even during errors, allowing users to recover
   - Changed the actions section from hiding completely when `selectedRepoStatus` is null to showing an error message when there's a `branchStatusError`

3. **`frontend/src/components/dialogs/tasks/GitActionsDialog.tsx`**
   - Extracted `error` from `useBranchStatus` and passes it to `GitOperations` as `branchStatusError`

4. **`frontend/src/pages/ProjectTasks.tsx`**
   - Updated `DiffsPanelContainer` to accept and pass `branchStatusError`
   - Extracted `branchStatusError` from `useBranchStatus` hook
   - Passes the error through to `DiffsPanelContainer`

5. **i18n translation files** (all 6 locales: en, es, ja, ko, zh-Hans, zh-Hant)
   - Added `git.errors.branchStatusUnavailable` translation string

### Behavior Changes

- **Before**: When branch status API failed, git operations (Merge, PR/Push, Rebase buttons) were completely hidden
- **After**:
  - An error message is shown: "Unable to fetch branch status. You can still change the target branch."
  - The "Change target branch" button remains enabled, allowing users to recover from deadlock situations
  - Users can see something went wrong instead of the UI silently failing

* Cleanup script changes for workspace cdcbb9c6-fc9f-45bf-9c30-0e432d06cccf

* Simplify useBranchStatus hook return

Return useQuery result directly instead of manually constructing an object.
The useQuery hook already returns all needed properties (data, error, isError, isLoading, refetch).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Restore hasConflictsCalculated check for change target branch button

The conflict check wasn't needed for the branch status error fix.
Keeping it prevents changing target branch during active git conflicts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex Netsch
2026-01-08 13:42:15 +00:00
committed by GitHub
parent b63c90186e
commit 67711e77f4
9 changed files with 32 additions and 10 deletions

View File

@@ -35,7 +35,9 @@ function GitActionsDialogContent({
task,
}: GitActionsDialogContentProps) {
const { t } = useTranslation('tasks');
const { data: branchStatus } = useBranchStatus(attempt.id);
const { data: branchStatus, error: branchStatusError } = useBranchStatus(
attempt.id
);
const { isAttemptRunning } = useAttemptExecution(attempt.id);
const { error: gitError } = useGitOperationsError();
const { repos, selectedRepoId } = useAttemptRepo(attempt.id);
@@ -83,6 +85,7 @@ function GitActionsDialogContent({
selectedAttempt={attempt}
task={task}
branchStatus={branchStatus ?? null}
branchStatusError={branchStatusError}
isAttemptRunning={isAttemptRunning}
selectedBranch={getSelectedRepoStatus()?.target_branch_name ?? null}
layout="vertical"

View File

@@ -35,6 +35,7 @@ interface GitOperationsProps {
selectedAttempt: Workspace;
task: TaskWithAttemptStatus;
branchStatus: RepoBranchStatus[] | null;
branchStatusError?: Error | null;
isAttemptRunning: boolean;
selectedBranch: string | null;
layout?: 'horizontal' | 'vertical';
@@ -46,6 +47,7 @@ function GitOperations({
selectedAttempt,
task,
branchStatus,
branchStatusError,
isAttemptRunning,
selectedBranch,
layout = 'horizontal',
@@ -463,7 +465,12 @@ function GitOperations({
)}
{/* Right: Actions */}
{selectedRepoStatus && (
{branchStatusError && !selectedRepoStatus ? (
<div className="flex items-center gap-2 text-xs text-destructive">
<AlertTriangle className="h-3.5 w-3.5" />
<span>{t('git.errors.branchStatusUnavailable')}</span>
</div>
) : selectedRepoStatus ? (
<div className={actionsClasses}>
<Button
onClick={handleMergeClick}
@@ -523,7 +530,7 @@ function GitOperations({
<span className="truncate max-w-[10ch]">{rebaseButtonLabel}</span>
</Button>
</div>
)}
) : null}
</div>
</div>
);

View File

@@ -257,7 +257,8 @@
"changeTargetBranch": "Failed to change target branch",
"pushChanges": "Failed to push changes",
"mergeChanges": "Failed to merge changes",
"rebaseBranch": "Failed to rebase branch"
"rebaseBranch": "Failed to rebase branch",
"branchStatusUnavailable": "Unable to fetch branch status. You can still change the target branch."
},
"pr": {
"open": "Open PR #{{number}}",

View File

@@ -235,7 +235,8 @@
"changeTargetBranch": "Error al cambiar rama de destino",
"mergeChanges": "Error al fusionar cambios",
"pushChanges": "Error al enviar cambios",
"rebaseBranch": "Error al hacer rebase de la rama"
"rebaseBranch": "Error al hacer rebase de la rama",
"branchStatusUnavailable": "No se puede obtener el estado de la rama. Aún puedes cambiar la rama de destino."
},
"labels": {
"taskBranch": "Rama de tarea"

View File

@@ -235,7 +235,8 @@
"changeTargetBranch": "ターゲットブランチの変更に失敗しました",
"mergeChanges": "変更のマージに失敗しました",
"pushChanges": "変更のプッシュに失敗しました",
"rebaseBranch": "ブランチのリベースに失敗しました"
"rebaseBranch": "ブランチのリベースに失敗しました",
"branchStatusUnavailable": "ブランチのステータスを取得できません。ターゲットブランチの変更は引き続き可能です。"
},
"labels": {
"taskBranch": "タスクブランチ"

View File

@@ -235,7 +235,8 @@
"changeTargetBranch": "대상 브랜치를 변경하지 못했습니다",
"mergeChanges": "변경사항을 병합하지 못했습니다",
"pushChanges": "변경사항을 푸시하지 못했습니다",
"rebaseBranch": "브랜치를 리베이스하지 못했습니다"
"rebaseBranch": "브랜치를 리베이스하지 못했습니다",
"branchStatusUnavailable": "브랜치 상태를 가져올 수 없습니다. 대상 브랜치는 여전히 변경할 수 있습니다."
},
"labels": {
"taskBranch": "작업 브랜치"

View File

@@ -270,7 +270,8 @@
"changeTargetBranch": "更改目标分支失败",
"pushChanges": "推送更改失败",
"mergeChanges": "合并更改失败",
"rebaseBranch": "变基分支失败"
"rebaseBranch": "变基分支失败",
"branchStatusUnavailable": "无法获取分支状态。您仍然可以更改目标分支。"
},
"pr": {
"open": "打开 PR #{{number}}",

View File

@@ -270,7 +270,8 @@
"changeTargetBranch": "變更目標分支失敗",
"pushChanges": "推送變更失敗",
"mergeChanges": "合併變更失敗",
"rebaseBranch": "重基底分支失敗"
"rebaseBranch": "重基底分支失敗",
"branchStatusUnavailable": "無法取得分支狀態。您仍然可以變更目標分支。"
},
"pr": {
"open": "開啟 PR #{{number}}",

View File

@@ -102,10 +102,12 @@ function DiffsPanelContainer({
attempt,
selectedTask,
branchStatus,
branchStatusError,
}: {
attempt: Workspace | null;
selectedTask: TaskWithAttemptStatus | null;
branchStatus: RepoBranchStatus[] | null;
branchStatusError?: Error | null;
}) {
const { isAttemptRunning } = useAttemptExecution(attempt?.id);
@@ -118,6 +120,7 @@ function DiffsPanelContainer({
? {
task: selectedTask,
branchStatus: branchStatus ?? null,
branchStatusError,
isAttemptRunning,
selectedBranch: branchStatus?.[0]?.target_branch_name ?? null,
}
@@ -284,7 +287,9 @@ export function ProjectTasks() {
const isTaskView = !!taskId && !effectiveAttemptId;
const { data: attempt } = useTaskAttemptWithSession(effectiveAttemptId);
const { data: branchStatus } = useBranchStatus(attempt?.id);
const { data: branchStatus, error: branchStatusError } = useBranchStatus(
attempt?.id
);
const rawMode = searchParams.get('view') as LayoutMode;
const mode: LayoutMode =
@@ -1000,6 +1005,7 @@ export function ProjectTasks() {
attempt={attempt}
selectedTask={selectedTask}
branchStatus={branchStatus ?? null}
branchStatusError={branchStatusError}
/>
)}
</div>