Skip to content

Standalone </think> close tags are rendered as assistant text during streaming #47

Description

@abstramagic27

Summary

During some multi-tool runs, the Browser Extension renders repeated standalone </think> lines inside the assistant message while the response is streaming.

These lines are reasoning/protocol boundary markers and should not be displayed as assistant text.

The persisted final assistant content may be clean, while the temporary streaming UI still contains the leaked markers.

Environment

  • Hermes Browser Extension: 0.1.11 / current main
  • Browser: Chromium-based
  • Connection mode: Local Gateway

User-visible behavior

The assistant card can temporarily look like this:

</think>

</think>

</think>

</think>

The actual answer may appear only after additional tool calls finish.

Deterministic reproduction

The behavior can be reproduced directly with the stream reducer:

let state = { text: '', finalized: false };

for (let index = 0; index < 4; index += 1) {
  state = reduceAssistantStreamText(state, {
    type: 'assistant.delta',
    data: { delta: '</think>\n\n' },
  });
}

console.log(state.text);

Expected behavior

Standalone reasoning close-tag lines should not be rendered as assistant text.

Normal assistant content must remain unchanged, including legitimate inline references such as:

The literal token `</think>` may appear in documentation.

Actual behavior

reduceAssistantStreamText() appends every assistant.delta directly to the current display text. Standalone closing tags are therefore rendered like normal assistant output.

The assistant.completed and run.completed reconciliation paths may also restore unsanitized content.

Affected file:

extension/lib/runtime-events.mjs

Suggested fix

Add a narrow display-boundary sanitizer that removes only complete lines composed entirely of orphan reasoning closing tags, for example:

  • </think>
  • </thinking>
  • </reasoning>
  • </thought>
  • </REASONING_SCRATCHPAD>

Apply it to the accumulated display text after:

  1. assistant.delta
  2. assistant.completed
  3. run.completed reconciliation

The filter should not remove inline text or code containing a literal tag.

This client-side guard is useful even if the malformed or unmatched close tag originally comes from the upstream Gateway stream: protocol markers should not leak into the user-facing assistant card.

Regression tests

Please add tests covering:

  1. Four tag-only deltas produce no visible assistant text.
  2. A tag-only line followed by a real answer preserves the answer.
  3. Sanitization also applies to assistant.completed.
  4. A tag split across multiple stream chunks is handled correctly.
  5. Inline literal references to </think> remain unchanged.
  6. Normal assistant text remains byte-for-byte unchanged.


This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️‍♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #47 either in the PR description or in a PR's comment.

🪙 Also, everyone can tip any user commenting /tip 20 @abstramagic27 (replace 20 with the amount, and @abstramagic27 with the user to tip).

📖 If you want to learn more, check out our documentation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    comp/docsPublic docs, README, security/privacy/data-flow docscomp/gatewayHermes gateway lifecycle, runtime connection, logs, local service behaviorcomp/modelsModel discovery, model picker, provider routing, runtime optionscomp/sidepanelSide panel UI, composer, messages, menus, settings shellneeds/browser-consoleNeeds sidepanel/service-worker console logs or network failure detailsp3Low: minor bug, polish, docs, duplicate, or follow-upplatform/chromeChrome-specific behavior or extension runtime issuestatus/needs-infoNeeds more user/reporter information before actionsweep:risk-releaseAutomation/review risk: release, package, manifest, tag, or asset drifttype/docsDocumentation improvementstype/supportSupport, setup, usage, or compatibility question

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions