Skip to content

(bug) A refused chat message can end up in both the composer and the transcript, or in neither #6697

Description

@mmabrouk

When the server refuses a message, the composer has already cleared, so the text has to be put back somewhere. The rule is that it lands in exactly one place: back in the composer, or on a flagged row in the transcript that says it was not sent. Never both, because then it can be sent twice. Never neither, because then it is gone.

Both failures are still reachable.

Both places

The restore writes the text into the editor and reads it straight back to confirm it took. The editor commits that write on a later microtask, so the read-back can say no while the text does in fact arrive a moment later. The caller then also keeps the flagged row, and the same message is in the composer and in the transcript.

Neither place

A send that carried only files, whose staged entries are no longer available, correctly reports that nothing was restored. If its echo row has already gone by then, there is no surface left showing the message at all.

There is a second route to the same end: the restore can put the attachments back before the original send's own cleanup runs, and that cleanup then removes the same entries it just restored.

Mobile has only one of the two

The mobile composer has no restorer wired through its seam, so a late refusal there always keeps the flagged row rather than refilling the input. The user has to select text out of a chat bubble to retry, which is the fiddliest interaction on the platform. Desktop restores the text and retrying is one keypress.

Where it shows

  1. Open the agent chat and send a message that the server refuses after the send request has already resolved.
  2. Watch where the text ends up. On the desktop it should be in the composer with a notice and no transcript row. On mobile it should be a flagged row with the note and an empty composer.
  3. Repeat with a message carrying only an attachment.

What to do

  • Make the restore an acknowledged transfer, so "the composer took it" is something the editor confirms rather than something inferred from a synchronous read.
  • Order the restore against the original send's cleanup, so the send cannot erase what the refusal just put back.
  • Give a file-only refusal a recovery surface when nothing can be restored.
  • Pass a restorer through the mobile seam so both hosts recover a late refusal the same way.

Follow-up from #6658. Related: #6700.

The refusal cells are refused-send.mjs and file-only-refusal.mjs in the PR's QA record at .claude/skills/release-conductor/runs/v0.115.3/qa/composer-latency/. Pass PREFIX=1 or the cell does not reproduce the shape a real refusal has.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions