Skip to content

Agentic UI: Keep a queued follow-up from vanishing when the agent is stopped - #4622

Open
shaunandrews wants to merge 3 commits into
trunkfrom
fix-queued-prompt-lost-on-stop
Open

Agentic UI: Keep a queued follow-up from vanishing when the agent is stopped#4622
shaunandrews wants to merge 3 commits into
trunkfrom
fix-queued-prompt-lost-on-stop

Conversation

@shaunandrews

@shaunandrews shaunandrews commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Related issues

  • Related to the queued-message report in Studio Code (no issue filed yet)

How AI was used in this PR

Claude Code investigated the report, reproduced the race in a unit test against the unmodified code, wrote the fix and the tests. I reviewed the diff.

Proposed Changes

Stopping the agent while a follow-up is queued sends that follow-up right away — the agent visibly starts working on it — but the message itself disappeared from the conversation.

  • The interrupted CLI child keeps winding down for up to two seconds after Stop. When its exit landed while the queued follow-up's run was still starting, the UI read it as "the current run ended" and refetched the transcript from disk. The new child hadn't written the prompt yet, so the refetch replaced the message the user had just watched send with disk state that didn't contain it. The transcript now only falls back to disk once nothing is about to write to that session.
  • Stopping a run also freed the session immediately, so the queued follow-up could fork a second CLI child while the interrupted one was still alive. Both resume the same session file and append from independently tracked positions, which forks the transcript's entry tree. Stopping now resolves only once the child is actually gone, so the next run starts against a settled transcript.
  • Events from an abandoned run no longer touch session state at all. The "ignore this run" marker was cleared on run.interrupted, which leaves the run.exited that always follows it unguarded.
  • A run now ends only on exit/close, never on error — that event also fires for a failed send()/kill() while the child is still alive and still writing. As a side effect this fixes a pre-existing hang: a child that fails to spawn emits close and never exit, so the session slot stayed occupied forever and the UI never got a run.exited.
  • A disk catch-up that has to be skipped because a run holds the session is now remembered and retried at the next safe point, so a start that then fails can't leave the transcript missing what the previous child wrote.

Both front ends (Desktop and the agentic UI) had the bug and both are fixed. No visual changes.

Testing Instructions

  1. Open a Studio Code session and send a prompt that keeps the agent busy for a while (e.g. "take a screenshot of my site and describe it").
  2. While it's working, type a second message and send it — it appears as a queued follow-up above the composer.
  3. Click Stop.
  4. The queued message should send, stay in the conversation, and the agent should start working on it. Before this change it appeared and then vanished, leaving the agent replying to nothing.
  5. Repeat with two queued follow-ups to confirm they still run in order.
  6. Also check a plain Stop with nothing queued: the turn should still close with "Interrupted by you" and the transcript should catch up with the aborted turn.

Automated: npm test -- apps/ui/src/data/queries/use-agent-run.test.tsx packages/common/ai/tests/run-manager.test.ts. The new keeps a queued prompt when the interrupted run exits mid-start case fails on trunk and passes here.

Not run: npm run e2e (no coverage of this flow) and the agent evals — this changes run-lifecycle plumbing, not agent-facing prompts, tools, or result shapes.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wpmobilebot

wpmobilebot commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 5f0454e vs trunk

app-size

Metric trunk 5f0454e Diff Change
App Size (Mac) 1436.08 MB 1436.09 MB +0.01 MB ⚪ 0.0%

site-editor

Metric trunk 5f0454e Diff Change
load 1194 ms 1208 ms +14 ms ⚪ 0.0%

site-startup

Metric trunk 5f0454e Diff Change
siteCreation 7492 ms 7510 ms +18 ms ⚪ 0.0%
siteStartup 3411 ms 3403 ms 8 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@shaunandrews
shaunandrews requested a balanced review from Copilot August 20, 2026 16:29
@shaunandrews shaunandrews changed the title Keep a queued follow-up from vanishing when the agent is stopped Agentic UI: Keep a queued follow-up from vanishing when the agent is stopped Aug 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents queued follow-ups from disappearing or overlapping an interrupted agent run.

Changes:

  • Waits for interrupted child processes to exit before continuing.
  • Guards optimistic transcripts against stale refetches and abandoned events.
  • Adds lifecycle and queued-handoff tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/common/ai/tests/run-manager.test.ts Tests interrupt completion behavior.
packages/common/ai/run-manager.ts Adds exit-aware interruption.
apps/ui/src/data/queries/use-agent-run.tsx Protects queued optimistic messages.
apps/ui/src/data/queries/use-agent-run.test.tsx Covers queued-stop races.
apps/studio/src/modules/ai-agent/run-manager.ts Propagates asynchronous interruption.
apps/studio/src/ipc-handlers.ts Awaits interrupted process exit.
apps/studio/src/components/studio-code-session/use-agent-run.tsx Applies transcript guards to Desktop.
apps/local/src/index.ts Delays interrupt responses until exit.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/common/ai/run-manager.ts Outdated
Comment thread apps/ui/src/data/queries/use-agent-run.tsx
Comment thread apps/studio/src/components/studio-code-session/use-agent-run.tsx
Comment thread apps/local/src/index.ts
Comment on lines +1646 to +1649
api.post( '/runs/:runId/interrupt', async ( req: Request, res: Response ) => {
// Responds only once the child is gone, so the UI can safely start the
// session's next run when this resolves.
await runManager.interruptAgentRun( req.params.runId );

@shaunandrews shaunandrews Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Claude Code — finding accurate, deliberately deferred to a follow-up. Leaving this thread open to track it.

The divergence is real, but it is not reachable today:

  • apps/hosted/src/agent-runs.ts is a separate implementation, not the shared @studio/common run manager, so it did not inherit the contract change either way.
  • Its only runtime is stubRuntime, which throws on start(). No agent run can begin on the hosted backend, so no overlapping follow-up can occur.
  • Hosted runs are intended for a per-session remote sandbox rather than the same local session JSONL, so the two-writer hazard this promise guards against does not transfer unchanged.

Worth mirroring the awaited-exit contract before a real runtime is injected. Tracking that separately rather than expanding this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants