Skip to content

fix: escape triple-backtick inside reasoning before wrapping in outer code block - #48477

Closed
Skywind5487 wants to merge 1 commit into
NousResearch:mainfrom
Skywind5487:fix/escape-reasoning-fences
Closed

fix: escape triple-backtick inside reasoning before wrapping in outer code block#48477
Skywind5487 wants to merge 1 commit into
NousResearch:mainfrom
Skywind5487:fix/escape-reasoning-fences

Conversation

@Skywind5487

Copy link
Copy Markdown
Contributor

Summary

When reasoning content contains ``` (e.g. the model quotes code in its thinking), wrapping it in an outer ``` display block causes the inner fence to break the outer one. The result is mangled markdown rendering on Discord, Slack, and other platforms.

Adds escape_code_fences_for_display() in gateway/stream_consumer.py. Called from gateway/run.py before reasoning text is wrapped in the outer ``` block — replaces each inner ``` with escaped backticks so the outer fence stays intact.

Testing

6 tests: no-fence passthrough, single/multiple fence escape, empty/null handling, and integration test verifying the outer fence is preserved after escaping.

This PR was authored by an AI assistant under the direction of @Skywind5487.

…e block

B1: When reasoning content contains ``` e.g. model quoting code in
its thinking, wrapping it in an outer ``` for display causes the
inner fence to break the outer block.

Adds escape_code_fences_for_display() in gateway/stream_consumer.py,
called from gateway/run.py before wrapping reasoning in the outer
``` display block.
Copilot AI review requested due to automatic review settings June 18, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes markdown rendering issues in gateway platforms (Discord/Slack/etc.) when the model’s reasoning text contains triple-backtick fences, by escaping inner ``` sequences before wrapping reasoning in an outer fenced code block.

Changes:

  • Add escape_code_fences_for_display() helper to replace inner ``` markers with a non-fence sequence suitable for embedding inside an outer fenced block.
  • Apply the escaping in gateway/run.py when show_reasoning is enabled and reasoning is prepended to the final response.
  • Add unit + integration-style tests covering no-op, single/multiple fences, empty/None handling, and outer-fence preservation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
gateway/stream_consumer.py Adds escape_code_fences_for_display() used to prevent nested code fences from breaking outer reasoning blocks.
gateway/run.py Escapes reasoning fences before wrapping reasoning in an outer ``` block for display.
tests/gateway/test_escape_reasoning_fences.py Adds tests validating escaping behavior and that the outer fence remains intact.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +50 to +65
def escape_code_fences_for_display(text: str) -> str:
"""Escape triple-backtick markers so text can be safely wrapped
inside an outer ``` code block without breaking the fence.

When reasoning content contains ``` (e.g. the model quotes code
in its thinking), wrapping it in an outer ``` for display causes
the inner fence to break the outer block. Solution: replace each
`` ``` `` with `` \\`\\`\\` `` before wrapping.

Returns:
The input text with each `` ``` `` replaced by `` \\`\\`\\` ``,
or the input unchanged if no triple-backticks are present.
"""
if not isinstance(text, str) or "```" not in text:
return text
return text.replace("```", "\\`\\`\\`")
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/discord Discord bot adapter platform/slack Slack app adapter platform/matrix Matrix adapter (E2EE) P2 Medium — degraded but workaround exists labels Jun 18, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating a real fence-rendering failure.

Problems

  • Current main now selects subtext, blockquote, or fenced code rendering in gateway/run.py:11807-11818 (introduced by 6cc07b6cd). The proposed unconditional escape in gateway/run.py would also alter subtext and blockquote reasoning even though neither creates an outer fence.
  • The submitted integration test only assembles an f-string; it does not cover the current gateway style-selection path at gateway/run.py:11784-11818.
  • The existing Copilot review correctly notes the helper's str -> str annotation conflicts with its intentional None passthrough and test.

Suggested changes

  • Apply the escape only in the current code branch before gateway/run.py:11818.
  • Add gateway-level cases for code, subtext, and blockquote styles.
  • Align the helper annotation with its accepted inputs.

This is an automated hermes-sweeper review.

Comment thread gateway/run.py
# break the outer code block used to render it.
display_reasoning = escape_code_fences_for_display(display_reasoning)
response = f"💭 **Reasoning:**\n```\n{display_reasoning}\n```\n\n{response}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main now chooses subtext, blockquote, or code after building display_reasoning (gateway/run.py:11807-11818). Apply this transformation only in the fenced code branch; otherwise Discord's default subtext and blockquote output gain visible backslashes despite having no enclosing fence.

"""
if not isinstance(text, str) or "```" not in text:
return text
return text.replace("```", "\\`\\`\\`")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is annotated str -> str, but this branch intentionally returns None and the added test asserts that behavior. Please either annotate the accepted/returned optional type or make the helper strictly string-only and remove the None case.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #70191 — your commit was cherry-picked/reapplied onto current main with your authorship preserved in git history: your reasoning-backtick escaping was cherry-picked, applied to the code-style branch.

Thanks for the contribution!

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/discord Discord bot adapter platform/matrix Matrix adapter (E2EE) platform/slack Slack app adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants