Severity
Normal — but high-nuisance on busy public topics: repeated Retry clicks flood the topic with duplicate bot posts, so to members it looks like the bot is “posting on its own / stuck in a loop.”
Platform
- Server: Discourse core
main(2026.7.x line), reproduced against latestorigin/main(dbf5eedac40, 2026-07-14).discourse-aibundled in core. - Client: reproduced from multiple browsers/devices (desktop Chrome, Android Chrome, iOS Safari) — it is server-side, so client is irrelevant.
Description
Actual result: In a public topic, clicking Retry on an AI-bot reply appends a brand-new bot post to the topic. The original reply is left untouched, so each Retry adds another post. N retries → N extra posts.
Expected result: Retry should regenerate the existing bot reply in place (replace its content), the same way it does inside an AI-bot PM / DM conversation. No new post should be created.
Steps to reproduce (from a fresh Discourse)
- Enable Discourse AI + AI bot; create/enable an AI agent (persona) with a bot user, and turn on “Allow topic mentions” so it can answer in public topics (
allow_topic_mentions). Add your user’s group to the agent’s allowed groups and toai_bot_allowed_groups. - In a public topic,
@mentionthe bot in a post so it replies. - On the bot’s reply, open the post admin/wrench menu and click Retry (the regenerate action →
POST /discourse-ai/ai-bot/post/:post_id/retry). - Observe: a new bot reply is appended instead of the existing one being regenerated. Repeat Retry → one extra post each time.
- For contrast, do the same inside a PM with the bot: Retry correctly regenerates in place (no duplicate). The divergence between PM and public topic is the tell.
Reproduces 100% of the time on public topics.
Root cause (code pointers, current origin/main)
BotController#retry_response(plugins/discourse-ai/app/controllers/discourse_ai/ai_bot/bot_controller.rb) enqueuesJobs::CreateAiReplywithreply_post_id: post.id— i.e. “regenerate this existing post.”Jobs::CreateAiReplyloads it asreply_postand passes it toPlayground#reply_to(..., existing_reply_post: reply_post).- In
Playground#reply_to(plugins/discourse-ai/lib/ai_bot/playground.rb):stream_reply = post.topic.private_message? if stream_reply.nil?→falsefor public topics (~L518).existing_reply_postis only consumed inside theif stream_replybranch (reply_post = existing_reply_post, ~L538).- The non-streaming
elsebranch (~L662) unconditionally doesPostCreator.create!(...), appending a new post and never touching the post being retried.
So for any non-PM topic, the “regenerate in place” intent from retry_response is silently dropped and a duplicate is created instead. (The same asymmetry means public-topic bot replies are non-streaming, which is the pre-existing behaviour; the bug is specifically that Retry’s existing_reply_post is ignored on that path.)
Suggested fix (direction only)
Honour existing_reply_post regardless of streaming — e.g. in the non-streaming branch, if existing_reply_post is present, revise it instead of calling PostCreator.create!; or decouple the “reuse existing post” decision from stream_reply.
Safe mode
N/A — server-side plugin behaviour, independent of themes/site JS.