Keep approved tool results when rebuilding Duo Chat history

What does this MR do and why?

In Duo Agentic Chat, an approved tool approval card (for example start_flow) shows "Cancelled" once you reopen the chat from history. The start_flow session card is missing too. Right after approving, live, the card is correct. It only breaks after reload.

The fix is on the Rails side. Chat history on reload is rebuilt from per-checkpoint ui_chat_log blobs by Gitlab::DuoWorkflow::ChannelValuesReconstructor. When a blob is a full snapshot, the reconstructor dropped any entry whose message_id had already been seen, using message_id alone as the key. An approval request and its tool result share the same message_id (the tool call id), so the tool result got dropped as a false repeat whenever the step that ran the approved tool landed in a snapshot. This MR dedupes on message_id plus message_type instead, so the request and the result are both kept, while a genuine repeat (same id, same type, from a restart re-seed) is still skipped.

Details and known limits

The previous fix, !256959 (merged), made the frontend match an approval request to its tool result by message_id. That fixed pairing when both entries are present. On reload, the tool result was not in the log at all, so the frontend still fell back to "Cancelled". I confirmed the log captured in the work item still renders both approved start_flow requests as cancelled on master.

Entries without an id keep the old whole-entry comparison. channel_changes uses the same dedup helper, so it gets the same fix.

Only the read side changes. Stored snapshots already contain the tool result, so existing sessions should render correctly after deploy with no data migration needed (not verified against production data).

Not covered by this MR:

  • It's not yet confirmed which gateway group-start trigger fires on approval resume: first checkpoint of a fresh saver, a stale-cache rewrite when resume_checkpoint_ts differs, or a real compaction. Checking the step_action of the tool step's ui_chat_log blob in a reported session (8330266) would confirm it.
  • Streamed pending tool cards also use message_type 'tool' with the same id. The web client doesn't stream tool calls today, so this isn't hit, but a client that does could still hide the executed result behind a snapshot. Adding status to the dedup key would cover that if it's ever needed.
  • The frontend transformer still treats "no result found" as cancelled. Recording the approval decision explicitly would be the durable follow-up, as a separate issue.

References

Screenshots or screen recordings

No visual change. This is a backend read fix; only the badge state after reload changes.

Before After
Approved card shows Cancelled after reload Approved card stays Approved after reload

How to set up and validate locally

  1. bin/rspec ee/spec/lib/gitlab/duo_workflow/channel_values_reconstructor_spec.rb
  2. In Duo Agentic Chat, start a flow (for example /flow:developer ...) and approve the start_flow request.
  3. Switch away and reopen the chat from history. The approval card should read Approved and the session card should be shown.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Closes #629915 (closed)

Edited by Tomas Bulva

Merge request reports

Loading
Loading