fix(cli): fix YOLO mode message handling and sync wedge#702
Open
seibe wants to merge 3 commits intoslopus:mainfrom
Open
fix(cli): fix YOLO mode message handling and sync wedge#702seibe wants to merge 3 commits intoslopus:mainfrom
seibe wants to merge 3 commits intoslopus:mainfrom
Conversation
… auto-approval Three interconnected bugs caused the App to permanently stop showing CLI messages when AskUserQuestion was auto-approved in Yolo (bypassPermissions) mode: A) Exclude AskUserQuestion from bypassPermissions so the App can present choices to the user instead of silently auto-selecting the first option B) Flush OutgoingMessageQueue before closing turn to prevent turn-end from arriving at the App before delayed tool call messages (orphan turns) C) Make tool call release atomic with enqueue to prevent head-of-line blocking race conditions in the message queue
- Add MAX_BATCH_SIZE (100) to flushOutbox to prevent oversized POST requests - Disallow AskUserQuestion tool when bypassPermissions mode is active
…ived In YOLO mode, the for-await loop blocks at nextMessage() after result messages while Claude auto-continues. Messages accumulated in the stream buffer but were never forwarded to the app. Added onMessageReceived callback that fires as soon as messages are read from stdout, bypassing the blocked iterator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a sync wedge (deadlock) between the CLI and App that occurred specifically in YOLO (bypass permissions) mode. Three interrelated issues caused messages to stop syncing.
Related issues
Addresses: #521, #446, #29, #206 — YOLO mode not working, permissions still being asked.
Related: #648 — YOLO preference not persisted (addressed separately by #689).
Comparison with similar PRs
permissionHandlerrace condition wherehandleToolCall()ignores itsmodeparameter, plus addsreset()fix. This is a valid general fix for YOLO mode (YOLO mode (bypassPermissions) not working - users still asked for permissions #521), but does not address the specificAskUserQuestionwedge: even with the mode parameter fixed,AskUserQuestionwould still be auto-approved in YOLO mode, and the app would hang waiting for user input that never comes. This PR prevents the wedge at multiple layers: (1) disallowsAskUserQuestionin the SDK'sdisallowedTools, (2) excludes it from auto-approval inpermissionHandler.ts, and (3) fixes the downstream message pipeline (immediate forwarding, atomic queue operations, flush-before-turn-end) that also contributed to the wedge. fix(permissions): resolve YOLO mode race condition (#521) #561 and this PR are complementary — fix(permissions): resolve YOLO mode race condition (#521) #561 fixes the general mode priority race, this PR fixes the AskUserQuestion-specific deadlock and message pipeline issues.reset()fix, no race condition fix, no tests). fix(permissions): resolve YOLO mode race condition (#521) #561 supersedes it.OutgoingMessageQueuehead-of-line blocking by skipping unreleased items. This PR takes a different approach: making the release-and-enqueue atomic within a single lock acquisition, which eliminates the race condition at its source rather than working around it. Both approaches have merit; this PR's atomic approach prevents the race entirely while fix: prevent head-of-line blocking in OutgoingMessageQueue #699's skip approach tolerates it.Root causes addressed
AskUserQuestionauto-approved in YOLO mode: Claude could callAskUserQuestionwhile in bypass-permissions mode. The tool was silently auto-approved, but no real user existed to answer, causing the App to hang waiting for input that would never come. Fix: disallowAskUserQuestionin the SDK'sdisallowedToolslist when in YOLO mode, and add a guard inpermissionHandler.tsto exclude it from auto-approval.Messages not forwarded until iterator advanced:
opts.onMessage()was called inside thefor awaitloop over Claude's output, but the loop could be blocked waiting fornextMessage()(user input). In YOLO auto-continuation mode, this meant assistant messages from completed turns were not synced to the App until the next turn started. Fix: introduce anonMessageReceivedcallback that fires immediately when messages are parsed from stdout, decoupled from the iterator consumer.Race condition in outgoing message queue: Tool-call release and next-message enqueue were separate operations under separate lock acquisitions. A released delayed message could be sent before the new message was enqueued, causing ordering issues. Fix: consolidate
releaseToolCallIdsinto theenqueue()call for atomic execution within a single lock.Turn-end sent before messages flushed: The
onReadycallback signaled turn completion before the outgoing message queue was fully flushed. Fix: makeonReadyasync andawait messageQueue.flush()before closing the turn.Outbox batch overflow:
flushOutboxsent all pending messages in a single request. During fast YOLO auto-continuations, the outbox could accumulate more messages than practical for a single POST. Fix: drain the outbox in a loop withMAX_BATCH_SIZE = 100.Changes by file
claudeRemote.ts: AddAskUserQuestiontodisallowedToolsin bypass mode; introduceonMessageReceivedfor immediate forwarding; widenonReadyto asyncclaudeRemoteLauncher.ts: Atomic tool-call release viareleaseToolCallIdsonenqueue(); asynconReadywith queue flushquery.ts: NewonMessageReceivedcallback onQueryclass, fired during stdout parsingOutgoingMessageQueue.ts: AddreleaseToolCallIdsoption toenqueue()for atomic release-and-enqueuepermissionHandler.ts: ExcludeAskUserQuestionfrom bypass auto-approvalapiSession.ts: Drain outbox in batched loop withMAX_BATCH_SIZE = 100Test plan
AskUserQuestion