Skip to content

fix(ai-chat): preserve server tool outputs for approval-responded state#993

Open
ferdousbhai wants to merge 4 commits intocloudflare:mainfrom
ferdousbhai:fix/merge-approval-responded-tool-state
Open

fix(ai-chat): preserve server tool outputs for approval-responded state#993
ferdousbhai wants to merge 4 commits intocloudflare:mainfrom
ferdousbhai:fix/merge-approval-responded-tool-state

Conversation

@ferdousbhai
Copy link

Summary

_mergeIncomingWithServerState only preserved server-side tool outputs when the incoming client part was in input-available state. This missed approval-responded — when a needsApproval tool is executed server-side (in onChatMessage after client approval), the server sets the part to output-available, but the client still holds approval-responded since it never received that state update.

On the next persistMessages call, the client's stale approval-responded overwrites the server's output-available + output, making the tool appear unexecuted. This causes duplicate tool execution if the server re-runs approved tools from message history.

Repro scenario

  1. Model calls a tool with needsApproval: true (e.g. create_doc)
  2. Client shows approval UI → user approves → server executes tool, sets output-available
  3. Model continues → calls another needsApproval tool (e.g. edit_doc)
  4. User approves second tool → client sends full message history via sendAutomaticallyWhen
  5. Client's message history still has the first tool in approval-responded (stale)
  6. persistMessages_mergeIncomingWithServerState checks part.state === "input-available"doesn't match approval-responded
  7. Server's output-available is overwritten → first tool appears unexecuted → gets re-run

Fix

Expand the merge condition to also restore output-available when the incoming part is in approval-responded state.

Test plan

  • Added test: preserves server-side tool outputs when client sends approval-responded state
  • All 230 existing tests pass

🤖 Generated with Claude Code

_mergeIncomingWithServerState only handled the input-available →
output-available transition when preserving server-side tool outputs.
This missed the approval-responded state: when a tool with
needsApproval is executed server-side (e.g. in onChatMessage after the
client approves it), the server sets the part to output-available. But
the client, which never received that state update, still holds
approval-responded. On the next persistMessages call the client's stale
state overwrites the server's output, causing the tool to appear
unexecuted and potentially be re-run.

Expand the merge condition to also restore output-available when the
incoming part is in approval-responded state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2026

🦋 Changeset detected

Latest commit: 9cfb69a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/ai-chat Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@993
npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/ai-chat@993
npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/codemode@993
npm i https://pkg.pr.new/cloudflare/agents/hono-agents@993

commit: eb251d0

Treat the client-side "approval-requested" state as a stale/in-progress state when merging incoming messages so the server's output-available state and output are preserved. Update the conditional and comment in packages/ai-chat/src/index.ts to include "approval-requested", and add a test in packages/ai-chat/src/tests/merge-server-state.test.ts that verifies a server-side output-available tool result is not overwritten when the client sends the same tool in approval-requested state.
Copy link
Contributor

@threepointone threepointone left a comment

Choose a reason for hiding this comment

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

lgtm, thank you!

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.

2 participants