From 3871fa810c2c949d4711741a07f9a14429dac825 Mon Sep 17 00:00:00 2001 From: paulbettner Date: Wed, 18 Feb 2026 20:23:36 -0500 Subject: [PATCH] fix(permissions): prevent yolo mode desync in approval handling (#1021) --- src/cli/App.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index fa13ec8..33db1bd 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -929,6 +929,11 @@ export default function App({ const [uiPermissionMode, setUiPermissionMode] = useState( permissionMode.getMode(), ); + const uiPermissionModeRef = useRef(uiPermissionMode); + useEffect(() => { + uiPermissionModeRef.current = uiPermissionMode; + }, [uiPermissionMode]); + const statusLineTriggerVersionRef = useRef(0); const [statusLineTriggerVersion, setStatusLineTriggerVersion] = useState(0); @@ -3016,6 +3021,7 @@ export default function App({ setUiRalphActive(false); if (wasYolo) { permissionMode.setMode("default"); + setUiPermissionMode("default"); } // Add completion status to transcript @@ -3040,6 +3046,7 @@ export default function App({ setUiRalphActive(false); if (wasYolo) { permissionMode.setMode("default"); + setUiPermissionMode("default"); } // Add status to transcript @@ -3988,6 +3995,14 @@ export default function App({ } // Check permissions for all approvals (including fancy UI tools) + // Ensure the singleton permission mode matches what the UI shows. + // This prevents rare races where the footer shows YOLO but approvals still + // get classified using the default mode. + const desiredMode = uiPermissionModeRef.current; + if (permissionMode.getMode() !== desiredMode) { + permissionMode.setMode(desiredMode); + } + const { needsUserInput, autoAllowed, autoDenied } = await classifyApprovals(approvalsToProcess, { getContext: analyzeToolApproval, @@ -5599,6 +5614,11 @@ export default function App({ } // There are pending approvals - check permissions (respects yolo mode) + const desiredMode = uiPermissionModeRef.current; + if (permissionMode.getMode() !== desiredMode) { + permissionMode.setMode(desiredMode); + } + const { needsUserInput, autoAllowed, autoDenied } = await classifyApprovals(existingApprovals, { getContext: analyzeToolApproval, @@ -5921,6 +5941,7 @@ export default function App({ justActivatedRalph = true; if (isYolo) { permissionMode.setMode("bypassPermissions"); + setUiPermissionMode("bypassPermissions"); } const ralphState = ralphMode.getState(); @@ -6612,6 +6633,7 @@ export default function App({ setUiRalphActive(true); if (isYolo) { permissionMode.setMode("bypassPermissions"); + setUiPermissionMode("bypassPermissions"); } const ralphState = ralphMode.getState(); @@ -8547,6 +8569,11 @@ ${SYSTEM_REMINDER_CLOSE} if (existingApprovals && existingApprovals.length > 0) { // There are pending approvals - check permissions first (respects yolo mode) + const desiredMode = uiPermissionModeRef.current; + if (permissionMode.getMode() !== desiredMode) { + permissionMode.setMode(desiredMode); + } + const { needsUserInput, autoAllowed, autoDenied } = await classifyApprovals(existingApprovals, { getContext: analyzeToolApproval,