fix(cli): prevent race condition in loop detection retry (#17916)

Co-authored-by: cynthialong0-0 <82900738+cynthialong0-0@users.noreply.github.com>
This commit is contained in:
skyvanguard
2026-03-10 15:41:16 -03:00
committed by GitHub
parent 13f78bd9eb
commit 7aae5435fa
2 changed files with 144 additions and 14 deletions
+34 -14
View File
@@ -216,7 +216,15 @@ export const useGeminiStream = (
const previousApprovalModeRef = useRef<ApprovalMode>(
config.getApprovalMode(),
);
const [isResponding, setIsResponding] = useState<boolean>(false);
const [isResponding, setIsRespondingState] = useState<boolean>(false);
const isRespondingRef = useRef<boolean>(false);
const setIsResponding = useCallback(
(value: boolean) => {
setIsRespondingState(value);
isRespondingRef.current = value;
},
[setIsRespondingState],
);
const [thought, thoughtRef, setThought] =
useStateAndRef<ThoughtSummary | null>(null);
const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] =
@@ -320,11 +328,14 @@ export const useGeminiStream = (
return (executingShellTool as TrackedExecutingToolCall | undefined)?.pid;
}, [toolCalls]);
const onExec = useCallback(async (done: Promise<void>) => {
setIsResponding(true);
await done;
setIsResponding(false);
}, []);
const onExec = useCallback(
async (done: Promise<void>) => {
setIsResponding(true);
await done;
setIsResponding(false);
},
[setIsResponding],
);
const {
handleShellCommand,
@@ -538,7 +549,7 @@ export const useGeminiStream = (
setIsResponding(false);
}
prevActiveShellPtyIdRef.current = activeShellPtyId;
}, [activeShellPtyId, addItem]);
}, [activeShellPtyId, addItem, setIsResponding]);
useEffect(() => {
if (
@@ -700,6 +711,7 @@ export const useGeminiStream = (
cancelAllToolCalls,
toolCalls,
activeShellPtyId,
setIsResponding,
]);
useKeypress(
@@ -952,7 +964,13 @@ export const useGeminiStream = (
setIsResponding(false);
setThought(null); // Reset thought when user cancels
},
[addItem, pendingHistoryItemRef, setPendingHistoryItem, setThought],
[
addItem,
pendingHistoryItemRef,
setPendingHistoryItem,
setThought,
setIsResponding,
],
);
const handleErrorEvent = useCallback(
@@ -1358,14 +1376,15 @@ export const useGeminiStream = (
async ({ metadata: spanMetadata }) => {
spanMetadata.input = query;
const queryId = `${Date.now()}-${Math.random()}`;
activeQueryIdRef.current = queryId;
if (
(streamingState === StreamingState.Responding ||
(isRespondingRef.current ||
streamingState === StreamingState.Responding ||
streamingState === StreamingState.WaitingForConfirmation) &&
!options?.isContinuation
)
return;
const queryId = `${Date.now()}-${Math.random()}`;
activeQueryIdRef.current = queryId;
const userMessageTimestamp = Date.now();
@@ -1452,7 +1471,7 @@ export const useGeminiStream = (
loopDetectedRef.current = false;
// Show the confirmation dialog to choose whether to disable loop detection
setLoopDetectionConfirmationRequest({
onComplete: (result: {
onComplete: async (result: {
userSelection: 'disable' | 'keep';
}) => {
setLoopDetectionConfirmationRequest(null);
@@ -1468,8 +1487,7 @@ export const useGeminiStream = (
});
if (lastQueryRef.current && lastPromptIdRef.current) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
submitQuery(
await submitQuery(
lastQueryRef.current,
{ isContinuation: true },
lastPromptIdRef.current,
@@ -1537,6 +1555,7 @@ export const useGeminiStream = (
maybeAddSuppressedToolErrorNote,
maybeAddLowVerbosityFailureNote,
settings.merged.billing?.overageStrategy,
setIsResponding,
],
);
@@ -1803,6 +1822,7 @@ export const useGeminiStream = (
isLowErrorVerbosity,
maybeAddSuppressedToolErrorNote,
maybeAddLowVerbosityFailureNote,
setIsResponding,
],
);