nesquena/hermes-webui

Regenerate response retains the original user row, then appends the same prompt again

Open

#6,611 opened on Jul 29, 2026

 (1 comment) (0 reactions) (0 assignees)Python (2,386 forks)github user discovery
bughelp wantedsessionsprint-candidate

Repository metrics

Stars
 (17,368 stars)
PR merge metrics
 (Avg merge 14h 31m) (314 merged PRs in 30d)

Description

Summary

Using Regenerate response on an errored assistant turn retains the original user row, then send() appends the same prompt as a new optimistic user row. The transcript therefore shows the prompt multiple times even though the user invoked one regeneration.

Reported by b3nw in Discord #report-bugs on 2026-07-28.

Source: https://discord.com/channels/1492983281338286121/1493325333980774622/1531883530437656716

"similar issue when using regenerate response to deal with an error, you get the same prompt text multiple times even though you're only actually executing the prompt a single time"

Environment in the report: WebUI exp-v0.52.153-dirty-a9b7cc19 · Experimental.

Reproduction on current master

Verified on current origin/master (0a4015975) by executing the actual extracted regenerateResponse() function with this minimal transcript:

[
  {role: 'user', content: 'same prompt'},
  {role: 'assistant', content: 'provider failed', _error: true},
]

Clicking Regenerate on assistant index 1 produced:

{
  "keep_count": 1,
  "messages": [
    {"role": "user", "content": "same prompt"},
    {"role": "user", "content": "same prompt", "_pending": true}
  ]
}

Root cause

static/ui.js::regenerateResponse() computes:

const assistantIdx = parseInt(row.dataset.msgIdx, 10);
const absoluteKeepCount = _oldestIdx + assistantIdx;

It then truncates to absoluteKeepCount, which preserves the preceding user row, puts that user's text back in the composer, and calls send():

S.messages = S.messages.slice(0, absoluteKeepCount);
$('msg').value = lastUserText;
await send();

send() correctly appends a new optimistic user row for the regenerated turn. Because the old one was retained, two identical user rows are now visible. The provider still executes the prompt only once: api/streaming.py removes a same-content tail user row from provider-facing history before starting the new turn. That explains the reporter's exact symptom: duplicate prompt bubbles, one execution.

The regenerate path already walks backward to find lastUserText, but it does not carry an explicit “reuse/replace this logical user turn” identity into the resend. The fix must preserve the original user's attachments and message metadata while producing one visible/persisted user row. A bare “truncate one row earlier and send the text again” is insufficient unless it deliberately transfers that metadata; relying on content equality is also unsafe because separate identical retries are valid.

Distinction from #6571

#6571 is a backend settlement bug where a failed visible prompt and a distinct identical retry are conflated during merge/final-answer evaluation. This issue is earlier and frontend-local: regenerateResponse() itself leaves the original user row in S.messages before send() appends another. Fixing one does not prove the other fixed.

Expected behavior

Regenerating an errored response should create one replacement user turn and one new assistant run. The visible and persisted transcript must contain only one copy of that prompt for the regenerated exchange.

Acceptance criteria

  • Regenerate explicitly reuses/replaces the selected exchange's logical user turn and sends the prompt once, preserving attachments and relevant message metadata.
  • No duplicate user bubble appears optimistically, after settlement, or after hard reload.
  • Regenerating a response in a truncated/partially-loaded session uses the correct absolute message index.
  • Earlier identical prompts in conversation history remain untouched.
  • A session switch during the async truncate cannot apply the regeneration to the newly visible session.
  • Add a browser/VM regression covering an errored assistant row and asserting one user row after regeneration.

Contributor guide