#17455: fix: strip content before orphan closing think tags
agents
size: XS
Cluster:
Model Reasoning Fixes
## Summary
Handle malformed model output that contains an orphan closing `</think>` tag (without a matching opening `<think>`) across both non-streaming and streaming sanitization paths.
Changes:
- `src/shared/text/reasoning-tags.ts`: orphan `</think>` now clears previously accumulated visible text before continuing.
- `src/agents/pi-embedded-subscribe.ts`: apply the same orphan-close behavior in streaming `stripBlockTags`.
- `src/shared/text/reasoning-tags.test.ts`: add/update tests for orphan-close behavior.
## Why
Some inference endpoints emit internal reasoning text followed by a bare `</think>`.
Without orphan-close handling, reasoning can leak into user-visible output.
## Scope
- One theme only: reasoning-tag sanitization for malformed `</think>` output.
- Applies to both message sanitization and streaming chunk sanitization for consistency.
## Out of Scope
- No protocol changes.
- No changes to normal, well-formed `<think>...</think>` handling semantics outside this bugfix.
## Testing
Ran locally:
- `pnpm vitest run --config vitest.unit.config.ts src/shared/text/reasoning-tags.test.ts` (41 passed)
- `pnpm vitest run --config vitest.e2e.config.ts src/agents/pi-embedded-subscribe.code-span-awareness.e2e.test.ts` (3 passed)
## AI Assistance
- AI-assisted: Yes
- Testing degree: Fully tested for touched paths (commands listed above)
- I confirm I understand what the code does: Yes
- Prompts/session logs: <link if available>
## PR Checklist
- [x] Local validation: ran `pnpm vitest run --config vitest.unit.config.ts src/shared/text/reasoning-tags.test.ts` (41 passed) and `pnpm vitest run --config vitest.e2e.config.ts src/agents/pi-embedded-subscribe.code-span-awareness.e2e.test.ts` (3 passed).
- [x] Focused scope (one change/theme per PR)
- [x] Clear "what" + "why" in description
- [x] AI-assistance transparency (if used): mention AI-assisted + testing level (+ prompts/logs if available)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds orphan closing tag handling to prevent reasoning text from leaking into user-visible output when models emit malformed `</think>` tags without matching opening tags. The fix is applied consistently across both non-streaming (`stripReasoningTagsFromText`) and streaming (`stripBlockTags`) sanitization paths.
Key changes:
- When an orphan `</think>` is encountered outside a thinking block, all previously accumulated visible text is cleared
- Content after the orphan close tag is preserved and returned
- Tests cover common malformed output patterns including inference endpoints that emit reasoning followed by bare `</think>`
The implementation correctly handles the stated use case where reasoning and orphan close appear in the same message/chunk. Streaming has an inherent limitation: if reasoning text is emitted in early chunks and an orphan `</think>` appears in a later chunk, the early chunks cannot be retroactively cleared. This is acceptable given the typical malformed output pattern described in the PR.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is focused, well-tested, and correctly handles the stated use case. The logic is consistent across both non-streaming and streaming paths. Tests verify expected behavior for orphan closing tags including edge cases like nested tags. No breaking changes to existing well-formed tag handling. The streaming limitation is inherent to the architecture and acceptable for the described malformed output pattern.
- No files require special attention
<sub>Last reviewed commit: e1709df</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#17304: feat(gemini): robust handling for non-XML reasoning headers (`Think...
by YoshiaKefasu · 2026-02-15
80.9%
#6678: fix(agents): support thinking tags with attributes
by GHesericsu · 2026-02-01
78.2%
#6685: fix: suppress thinking leak for Synthetic reasoning models
by AkiLetschne · 2026-02-01
78.1%
#5982: fix: sanitize model reasoning blocks from Discord output
by Ambar-13 · 2026-02-01
77.7%
#19407: fix(agents): strip thinking blocks on cross-provider model switch (...
by lailoo · 2026-02-17
77.6%
#6559: Fix LiteLLM reasoning-tag handling + fallback to <think> content
by Najia-afk · 2026-02-01
77.0%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
77.0%
#23462: fix: extract thinking blocks as fallback in extractTextFromChatContent
by nszhsl · 2026-02-22
76.9%
#18935: fix(agents): suppress reasoning blocks from channel delivery
by BinHPdev · 2026-02-17
76.6%
#10430: fix: remove Minimax from isReasoningTagProvider
by echoedinvoker · 2026-02-06
76.5%