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_tsdiffers, or a real compaction. Checking thestep_actionof the tool step'sui_chat_logblob in a reported session (8330266) would confirm it. - Streamed
pendingtool cards also usemessage_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
- Issue: #629915 (closed)
- Earlier frontend fix this completes: !256959 (merged)
- Earlier report of swapped badges: #619249
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
bin/rspec ee/spec/lib/gitlab/duo_workflow/channel_values_reconstructor_spec.rb- In Duo Agentic Chat, start a flow (for example
/flow:developer ...) and approve thestart_flowrequest. - 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.
Closes #629915 (closed)