Solve task follow up section infinite loop (#1955)

**Root cause:** The `switch` statement was missing a `default` case. When `typ.type` didn't match any handled case, the loop continued forever because `curr` was never updated.

**Changes made:**

1. **`vibe-kanban/frontend/src/utils/executor.ts`** (line 59-60): Added `default:` to fall through with `ScriptRequest`, advancing to the next action for any unhandled types.

2. **`vibe-kanban/frontend/src/components/tasks/TaskFollowUpSection.tsx`**:
   - Removed duplicate local `extractProfile` function (17 lines)
   - Added import from `@/utils/executor`
   - Updated usage to `extractProfileFromAction`

3. **`vibe-kanban/frontend/src/components/NormalizedConversation/RetryEditorInline.tsx`**:
   - Removed duplicate local `extractProfile` function (17 lines)
   - Added import from `@/utils/executor`
   - Updated usage to `extractProfileFromAction`

Both type checks (TypeScript and Rust/cargo) pass successfully.
This commit is contained in:
Louis Knight-Webb
2026-01-12 11:04:06 +00:00
committed by GitHub
parent 3c327216a4
commit 8e7eb54313
3 changed files with 6 additions and 45 deletions

View File

@@ -14,7 +14,7 @@ import { useUserSystem } from '@/components/ConfigProvider';
import { useBranchStatus } from '@/hooks/useBranchStatus';
import { useVariant } from '@/hooks/useVariant';
import { useRetryProcess } from '@/hooks/useRetryProcess';
import type { ExecutorAction, ExecutorProfileId } from 'shared/types';
import { extractProfileFromAction } from '@/utils/executor';
export function RetryEditorInline({
attempt,
@@ -46,26 +46,7 @@ export function RetryEditorInline({
(p) => p.id === executionProcessId
);
if (!process?.executor_action) return null;
const extractProfile = (
action: ExecutorAction | null
): ExecutorProfileId | null => {
let curr: ExecutorAction | null = action;
while (curr) {
const typ = curr.typ;
switch (typ.type) {
case 'CodingAgentInitialRequest':
case 'CodingAgentFollowUpRequest':
return typ.executor_profile_id;
case 'ScriptRequest':
curr = curr.next_action;
continue;
}
}
return null;
};
return extractProfile(process.executor_action)?.variant ?? null;
return extractProfileFromAction(process.executor_action)?.variant ?? null;
}, [attemptData.processes, executionProcessId]);
const { selectedVariant, setSelectedVariant } = useVariant({

View File

@@ -47,11 +47,8 @@ import WYSIWYGEditor from '@/components/ui/wysiwyg';
import { useRetryUi } from '@/contexts/RetryUiContext';
import { useFollowUpSend } from '@/hooks/useFollowUpSend';
import { useVariant } from '@/hooks/useVariant';
import type {
DraftFollowUpData,
ExecutorAction,
ExecutorProfileId,
} from 'shared/types';
import type { DraftFollowUpData, ExecutorProfileId } from 'shared/types';
import { extractProfileFromAction } from '@/utils/executor';
import { buildResolveConflictsInstructions } from '@/lib/conflicts';
import { useTranslation } from 'react-i18next';
import { useScratch } from '@/hooks/useScratch';
@@ -152,29 +149,11 @@ export function TaskFollowUpSection({
// Variant selection - derive default from latest process
const latestProfileId = useMemo<ExecutorProfileId | null>(() => {
if (!processes?.length) return null;
const extractProfile = (
action: ExecutorAction | null
): ExecutorProfileId | null => {
let curr: ExecutorAction | null = action;
while (curr) {
const typ = curr.typ;
switch (typ.type) {
case 'CodingAgentInitialRequest':
case 'CodingAgentFollowUpRequest':
return typ.executor_profile_id;
case 'ScriptRequest':
curr = curr.next_action;
continue;
}
}
return null;
};
return (
processes
.slice()
.reverse()
.map((p) => extractProfile(p.executor_action ?? null))
.map((p) => extractProfileFromAction(p.executor_action ?? null))
.find((pid) => pid !== null) ?? null
);
}, [processes]);

View File

@@ -57,6 +57,7 @@ export function extractProfileFromAction(
case 'CodingAgentFollowUpRequest':
return typ.executor_profile_id;
case 'ScriptRequest':
default:
curr = curr.next_action;
continue;
}