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
- Open the agent chat and send a message that the server refuses after the send request has already resolved.
- 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.
- Repeat with a message carrying only an attachment.
What to do
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.
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
What to do
Follow-up from #6658. Related: #6700.
The refusal cells are
refused-send.mjsandfile-only-refusal.mjsin the PR's QA record at.claude/skills/release-conductor/runs/v0.115.3/qa/composer-latency/. PassPREFIX=1or the cell does not reproduce the shape a real refusal has.