fix: keep cloud user message visible while log echo is pending#1890
Draft
VojtechBartos wants to merge 12 commits intomainfrom
Draft
fix: keep cloud user message visible while log echo is pending#1890VojtechBartos wants to merge 12 commits intomainfrom
VojtechBartos wants to merge 12 commits intomainfrom
Conversation
When a follow-up was queued during the final turn of a cloud run, the terminal-status branch in handleCloudTaskUpdate silently dropped it — the auto-flush above had also raced ahead with sendCloudPrompt against a now-finished run, so the user_message command never reached the cloud. Skip the auto-flush when this update brings the run terminal, and have the terminal-status branch dequeue any pending messages and replay them via resumeCloudRun (which spins up a fresh task run carrying the prompt as pending_user_message). Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
After the queue auto-flush dequeues a message, sendCloudPrompt fires the user_message command but the cloud log stream takes a moment to echo back the session/prompt event. Without an optimistic placeholder, the bubble vanishes during that window and the user thinks the message was dropped. Mirror the local optimistic flow: append a user_message item before the mutate, drop it when the echo arrives, and clear on send failure or retry exhaustion. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
0b65c88 to
8ff7273
Compare
…lush Two race conditions could still drop the message after the previous fix: 1. The auto-flush mutate fires while the cloud is in_progress, but the run terminates before the cloud handles the user_message — the cloud rejects the command and sendQueuedCloudMessages eats the message in retry exhaustion. Catch the failure in sendCloudPrompt and, if the cloud has gone terminal in the meantime, replay through resumeCloudRun. 2. The mutate succeeds but the cloud terminates before echoing back the session/prompt request — the optimistic bubble hangs, the message is silently lost on the cloud side. When the terminal-status block runs with no queue but a pending optimistic user_message, reconstruct the prompt from the optimistic items and replay through resumeCloudRun. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
…ress queue The cloud's /command/ endpoint accepts user_message commands at any time once the run is in_progress and queues them server-side until the agent turn ends — same shape as ACP's session/prompt queueing in the local agent. The renderer was mirroring that queueing locally, which kept introducing race conditions: messages got lost when the run terminated between dequeue and mutate, when the mutate succeeded but the cloud silently terminated before processing, or anywhere in between. Send straight to cloud as soon as the user submits. Show the message in chat right away as an optimistic bubble; if a prior turn is still in flight, mark `isQueued: true` so the bubble renders with the same "queued" affordance the user is used to. The real session/prompt event arrives via the log stream and replaces the optimistic item. The local messageQueue now exists only for the sandbox-not-ready window (cloudStatus !== "in_progress"); handleCloudTaskUpdate flushes it through the regular send path once the run goes in_progress, and the terminal-status branch falls back to resumeCloudRun if anything is left local when the run finishes. Removes ~150 lines of auto-flush, retry, and replay machinery. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
The previous attempt rendered an optimistic bubble with an isQueued flag to mimic the queued affordance. In practice the bubble was either invisible or vanished too quickly for the user to perceive. Drop the flag and lean on the existing messageQueue / QueuedMessageView instead: when sendCloudPrompt sees a prior turn is in flight, push the message to messageQueue (which renders the familiar queued bubble at the bottom of the chat) AND fire the user_message command to the cloud immediately. When the cloud's session/prompt echo for our message lands in the log stream, handleCloudTaskUpdate pops the oldest queued message — the agent has just started processing it, and the real event takes over the visual. For the idle case (no prior turn), keep the existing optimistic bubble to fill the dequeue→echo gap. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
Match the local agent's behavior: when the user submits during a running cloud turn, hold the message in the local messageQueue (queued bubble shown via QueuedMessageView) and only POST the user_message command after the prior turn's end_turn lands in the log stream. handleCloudTaskUpdate's auto-flush picks up the queued message once isPromptPending flips to false, drains the queue, and routes through sendCloudPrompt (which now adds an optimistic bubble for the dispatch gap until the cloud's session/prompt echo arrives). The terminal-status branch still resumes through a fresh run if the cloud goes terminal with anything pending; the catch in sendCloudPrompt still falls back to resumeCloudRun if the user_message mutate fails because the run terminated mid-flight. Both paths protect the user's message from being silently dropped. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
The hold-locally-then-dispatch design dropped messages whenever the cloud's lifecycle didn't line up with the renderer's expectations. The immediate-dispatch design got the message through but the queued affordance only flashed for ~1s. Combine the two: enqueue the message locally (queued bubble visible) AND fire the user_message command to the cloud right away. The cloud queues server-side. handleCloudTaskUpdate now pops one entry from messageQueue per completed turn (matched against currentPromptId so nested calls don't cause spurious pops), so the bubble disappears exactly when the cloud picks our message up — same UX as the local agent. Cleanup on failure paths: if the run terminates while the mutate is in flight, fall back to resumeCloudRun (queue is dropped along with the session). For non-terminal failures, remove the queued entry the user saw so it doesn't outlive the failure toast. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
Sending user_message during an in-flight cloud turn preempts the prior turn on the cloud and breaks its response. Hold the follow-up locally as a queued bubble (matching the local agent UX) and only fire the user_message mutate after the prior turn's end_turn lands. handleCloudTaskUpdate's auto-flush re-enters sendCloudPrompt with priorTurnInFlight=false once isPromptPending flips, at which point the mutate dispatches into a now-idle agent. Skip the auto-flush if the same update brings the run to a terminal status — the terminal-status block below replays via resumeCloudRun instead. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
…ssage The auto-flush was firing user_message mutates after the prior turn's end_turn lands. The cloud accepts the command and queues it server-side but the run is already winding down — by the time the agent would pick the message up the run has terminated, and the message is silently dropped. Replace the auto-flush mutate path with resumeCloudRun, which is what the cloud's resume API is actually designed for: a fresh task run inheriting the prior conversation state, with our prompt carried in as `pending_user_message`. The cloud then runs a normal turn against the queued message and emits its session/prompt + assistant content like any other run. Direct user input (no prior turn) keeps using sendCloudPrompt's mutate path; the existing catch-side fallback to resumeCloudRun handles the case where that mutate fails because the run terminated mid-flight. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
The auto-flush was dispatching resumeCloudRun the moment end_turn arrived in the log stream, but the cloud's resume API requires the prior run to actually be in a terminal state. End_turn alone doesn't guarantee that — the run may still be wrapping up server-side, in which case runTaskInCloud rejects the resume, the catch toasts, and the message is lost. Drop the auto-flush. Let the queue sit until the cloud's status update brings the run terminal, at which point the terminal-status block dequeues + resumeCloudRun. By then the prior run is genuinely terminal and the resume succeeds. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
Combine the auto-flush + resume strategies. After end_turn arrives, auto-flush dequeues the queue and calls sendCloudPrompt, which fires the user_message mutate. If the cloud accepts, the agent processes the message normally. If the mutate fails for any reason — terminal status, transitional wind-down, network blip — sendCloudPrompt now unconditionally falls back to resumeCloudRun, which spins up a fresh task run carrying the prompt as `pending_user_message` and inheriting the prior conversation state. This covers both single-shot clouds (mutate rejected, resume kicks in) and multi-turn clouds (mutate accepted, agent processes immediately). The terminal-status block stays as a final safety net for in-flight optimistic items when the run goes terminal mid-mutate. Adds an info log on auto-flush so we can confirm it's firing in DevTools when reproducing. Generated-By: PostHog Code Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
The user_message sendCommand path is the wrong tool for queued cloud
follow-ups. While a turn is in flight it preempts the prior response;
after end_turn the cloud either rejects it or silently drops it as the
run winds down. Both modes leave the user with a vanished bubble and
no agent reply.
Skip the mutate entirely. After end_turn, dequeue and call
resumeCloudRun, which uses the same /tasks/{id}/run/ endpoint the
initial task run uses, with `resume_from_run_id` and
`pending_user_message`. The cloud creates a new run that inherits the
prior conversation state and starts a normal turn against the queued
prompt — exactly how follow-ups are designed to work in this cloud's
single-turn-per-run model.
If the resume call fails the auto-flush's outer .catch logs and shows
a toast so the failure is visible to the user instead of silent.
Generated-By: PostHog Code
Task-Id: 8aeaf6f9-8b18-426a-9453-c668ca17d227
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
When you type a follow-up during a cloud run that's mid-turn, the message renders as a queued bubble — but as soon as the turn completes, the bubble disappears for several seconds before the cloud's
session/promptecho arrives in the log stream. The user sees their message vanish.This mirrors the optimistic-bubble pattern that already exists on the local execution path:
sendCloudPromptnow appends an optimisticuser_messagebefore firing the API call, and clears it if the send fails.sendQueuedCloudMessagesconverts each dequeued queued message into an optimistic bubble (first attempt only — retries don't stack), and clears them after retry exhaustion.handleCloudTaskUpdateclears optimistic items when newly appended log entries contain asession/promptecho. MirrorsreplaceOptimisticWithEventon the local path; cleared in bulk because cloud entries arrive batched.Test plan
pnpm --filter code test— 945 passed (2 new tests for the cloud optimistic + failure paths)pnpm --filter code typecheckpnpm lintin_progressCreated with PostHog Code