From 4e4043d0e42408576f8c44c35bd4d90e8612e51b Mon Sep 17 00:00:00 2001 From: SSU-WEI HUANG Date: Thu, 18 Jun 2026 10:11:51 +0800 Subject: [PATCH] fix(claude): flush OutgoingMessageQueue before consuming next user turn (#909) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OutgoingMessageQueue.scheduleProcessing() defers socket.emit() via setTimeout(fn,0) — a macrotask. The Claude SDK's nextMessage() callback runs in a microtask chain, which executes before that macrotask fires. This means messages-consumed for turn N+1 can be sent to the hub before the queued agent messages from turn N have been emitted. The hub stamps invokedAt on the N+1 user message at receive time, and then stores the late-arriving agent messages with created_at > invokedAt_N+1. Since compareMessages sorts by invokedAt ?? createdAt ascending, those agent messages sort permanently below the N+1 user message. Fix: await messageQueue.flush() at the top of nextMessage() so all pending outgoing agent messages are sent through the socket before messages-consumed is dispatched. Closes #908 via [HAPI](https://hapi.run) Co-authored-by: HAPI --- cli/src/claude/claudeRemoteLauncher.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli/src/claude/claudeRemoteLauncher.ts b/cli/src/claude/claudeRemoteLauncher.ts index c5f4a327..f3ca0eb7 100644 --- a/cli/src/claude/claudeRemoteLauncher.ts +++ b/cli/src/claude/claudeRemoteLauncher.ts @@ -306,6 +306,14 @@ class ClaudeRemoteLauncher extends RemoteLauncherBase { return permissionHandler.isAborted(toolCallId); }, nextMessage: async () => { + // Flush any pending outgoing messages before consuming the next user + // turn. Without this, scheduleProcessing()'s setTimeout(fn,0) fires + // after the microtask that sends messages-consumed, causing the hub + // to stamp invokedAt on the next user message before it stores the + // current turn's queued agent messages — making them sort permanently + // below the next user message. + await messageQueue.flush(); + if (pending) { let p = pending; pending = null;