← Back to PRs

#17455: fix: strip content before orphan closing think tags

by jwt625 open 2026-02-15 19:40 View on GitHub →
agents size: XS
## 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