From 5befa96f0bcebc991177264fb99ac64d7630b787 Mon Sep 17 00:00:00 2001 From: Eric Seastrand Date: Thu, 12 Mar 2026 07:18:35 -0500 Subject: [PATCH] fix(happy-cli): unblock SDK messages while waiting for user input The for-await loop in claudeRemote awaited nextMessage() synchronously, which blocked all SDK messages (background task completions, system notifications) from being forwarded to the phone until the user sent their next message. Switch to a non-blocking .then() pattern so the loop keeps iterating and forwarding events while waiting for input. Fixes head-of-line blocking reported in GitHub #639. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --- packages/happy-cli/src/claude/claudeRemote.ts | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/happy-cli/src/claude/claudeRemote.ts b/packages/happy-cli/src/claude/claudeRemote.ts index d93215c8cf..3f3c3c53ee 100644 --- a/packages/happy-cli/src/claude/claudeRemote.ts +++ b/packages/happy-cli/src/claude/claudeRemote.ts @@ -165,6 +165,11 @@ export async function claudeRemote(opts: { try { logger.debug(`[claudeRemote] Starting to iterate over response`); + // Track whether we're already waiting for user input. + // This lets the for-await loop keep forwarding SDK messages (e.g. background + // task completions) to the phone instead of blocking on nextMessage(). + let waitingForInput = false; + for await (const message of response) { logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message); @@ -192,7 +197,7 @@ export async function claudeRemote(opts: { // Handle result messages if (message.type === 'result') { updateThinking(false); - logger.debug('[claudeRemote] Result received, exiting claudeRemote'); + logger.debug('[claudeRemote] Result received'); // Send completion messages if (isCompactCommand) { @@ -206,14 +211,26 @@ export async function claudeRemote(opts: { // Send ready event opts.onReady(); - // Push next message - const next = await opts.nextMessage(); - if (!next) { - messages.end(); - return; + // Wait for next user message WITHOUT blocking the for-await loop. + // If we awaited nextMessage() here, the loop would stall and any SDK + // messages emitted while waiting (background task completions, system + // notifications) would pile up unsent until the user types something. + if (!waitingForInput) { + waitingForInput = true; + opts.nextMessage().then(next => { + waitingForInput = false; + if (!next) { + messages.end(); + } else { + mode = next.mode; + messages.push({ type: 'user', message: { role: 'user', content: next.message } }); + } + }).catch(err => { + waitingForInput = false; + logger.debug('[claudeRemote] nextMessage() error, ending', err); + messages.end(); + }); } - mode = next.mode; - messages.push({ type: 'user', message: { role: 'user', content: next.message } }); } // Handle tool result