From 159dc8699ccd806fe3a770ea3531de529b9ed9df Mon Sep 17 00:00:00 2001 From: aadilshaikh123 Date: Wed, 8 Apr 2026 20:20:55 +0530 Subject: [PATCH] fix: prevent prompt_async race condition on idle sessions Add a check before exiting the session loop to detect if new messages arrived while the loop was running. This handles a race condition where prompt_async messages on idle sessions were created but not reliably acted upon. The issue occurs when: 1. Session completes work and transitions to idle (runner cleanup start) 2. Concurrently, prompt_async with noReply: false creates a message and calls loop() 3. A new runner is created to process the message 4. But the loop exits before detecting the newly created message The fix checks if new user messages exist beyond the last assistant message before exiting the loop. If found, continues the loop to process them. This ensures messages from prompt_async reliably trigger assistant responses even when the session is idle, which is critical for async communication patterns like relay-mesh multi-agent systems. Related issues: - Background agents ignore initial prompt - stuck until manually messaged - Race condition between session cancel and Todo Continuation / Question dismiss - TUI doesn't render messages from prompt_async endpoint - /session/status not reporting properly after prompt_async --- packages/opencode/src/session/prompt.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index c29733999214..660cb5cefd93 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1378,6 +1378,28 @@ NOTE: At any point in time through this workflow you should feel free to ask the !hasToolCalls && lastUser.id < lastAssistant.id ) { + // Before exiting, check if new messages arrived while we were running + // This handles the race condition in prompt_async where messages can arrive + // while the runner is transitioning between states + // See: https://github.com/anomalyco/opencode/issues/... + const latestMsgs = yield* MessageV2.filterCompactedEffect(sessionID) + const newUserMsg = latestMsgs.findLast( + (m) => m.info.role === "user" && m.info.id > lastAssistant.id, + ) + + // If a new user message arrived while we were running, continue the loop + // instead of exiting, so we can process it + if (newUserMsg) { + log.info("detected new message during exit, continuing loop", { + sessionID, + lastAssistantId: lastAssistant.id, + newMsgId: newUserMsg.info.id, + }) + // Reset step counter for the new message + step = 0 + continue + } + log.info("exiting loop", { sessionID }) break }