Skip to content

fix(server): unwrap JSON envelope leaking into generated titles - #7413

Open
sideeffffect wants to merge 6 commits into
pingdotgg:mainfrom
sideeffffect:fix/thread-title-json-envelope
Open

fix(server): unwrap JSON envelope leaking into generated titles#7413
sideeffffect wants to merge 6 commits into
pingdotgg:mainfrom
sideeffffect:fix/thread-title-json-envelope

Conversation

@sideeffffect

@sideeffffect sideeffffect commented Aug 18, 2026

Copy link
Copy Markdown

Problem

Sometimes T3 Code names a thread with the raw JSON envelope — e.g. the sidebar shows {"title": "the actual name"} instead of just the actual name. Some provider models ignore the structured-output contract and emit the whole JSON object as the value of the title field. The shared title sanitizers only stripped surrounding quotes, so that JSON string leaked straight through into the label.

Fix

Add unwrapJsonEnvelopeTitle in TextGenerationUtils, which detects a JSON object whose title is a string and unwraps the inner value:

  • recurses to handle double-wrapping ({"title": "{\"title\": \"…\"}"}),
  • tolerates surrounding prose / code fences via the existing extractJsonObject helper,
  • leaves plain titles that merely contain {/} untouched.

It's applied in both sanitizeThreadTitle and sanitizePrTitle, so every provider (Claude, Codex, Cursor, Grok, OpenCode) benefits from the single shared sink. Added focused unit tests covering the wrapped, double-wrapped, code-fenced, and plain-braces cases.

Testing

  • vp test run apps/server/src/textGeneration/TextGenerationPrompts.test.ts — 25 passed
  • targeted tsgo --noEmit and vp lint on the changed files — clean

Done with Claude Opus 4.8 via Claude Code.


Note

Low Risk
Localized display normalization in text-generation utils with conservative parse rules and extensive tests; no auth, persistence, or API contract changes.

Overview
Fixes thread and PR labels sometimes showing raw structured-output junk (e.g. {"title": "…"}) when models put the whole JSON object in the title field.

Adds unwrapJsonEnvelopeTitle in TextGenerationUtils, which recursively JSON.parses the trimmed value: unwraps JSON strings, objects with a single string field (any key), or multi-string objects when a title key exists; stops at depth 8; leaves invalid JSON, ambiguous multi-string objects without title, and prose with embedded {…} unchanged.

sanitizeThreadTitle and sanitizePrTitle now run through this helper before their existing line/quote/truncation logic. Unit tests cover wrapped, double-wrapped, JSON-string-encoded, ambiguous, and prose cases for both sanitizers.

Reviewed by Cursor Bugbot for commit 33c550f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix JSON envelope leaking into generated titles in sanitizeThreadTitle and sanitizePrTitle

  • Adds unwrapJsonEnvelopeTitle in TextGenerationUtils.ts to strip JSON object/string envelopes from raw titles before sanitization, including nested and double-encoded forms.
  • When an object has multiple string fields, the unwrapper prefers a title field; ambiguous objects with no title field are left unchanged to avoid false positives.
  • Both sanitizeThreadTitle and sanitizePrTitle now pass input through unwrapJsonEnvelopeTitle before applying existing normalization logic.
  • Behavioral Change: titles previously returned as raw JSON strings or objects (e.g. {"title":"My PR"}) will now yield the inner string value.

Macroscope summarized 33c550f.

Some provider models ignore the structured-output contract and emit the
whole JSON envelope as the field value, so a generated thread title (or PR
title) comes back as the literal string `{"title": "the actual name"}`
instead of `the actual name`. The shared sanitizers only stripped outer
quotes, so that JSON leaked straight into the sidebar label.

Add `unwrapJsonEnvelopeTitle`, which detects a JSON object whose `title` is
a string and unwraps it (recursing for double-wrapping, tolerating
surrounding prose or code fences), and apply it in both
`sanitizeThreadTitle` and `sanitizePrTitle`. Plain titles that merely
contain braces are left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a226aa26-328d-4ec9-b0a2-0ccc495d46b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 18, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 18, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — Bug fix for unwrapping JSON envelopes in generated titles. An unresolved Medium severity finding identifies a bug in the stripEnvelopeDecorations ordering that causes fenced+quoted titles to not unwrap correctly. The author has recent contributions to this file.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

…ny key

Models don't always label the wrapped value `title` — it can come back as
`name`, `summary`, etc. When the emitted JSON object has exactly one string
value, take it regardless of the key name. Only fall back to preferring a
`title` key when several string values make the choice ambiguous.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review August 18, 2026 16:01

Dismissing prior approval to re-evaluate 8567d51

Comment thread apps/server/src/textGeneration/TextGenerationUtils.ts Outdated
Comment thread apps/server/src/textGeneration/TextGenerationUtils.ts Outdated
sideeffffect and others added 2 commits August 18, 2026 18:07
Clarify that the sole-string-value rule ignores non-string fields: an
object like `{"title": "Fix bug", "confidence": 0.9}` is unambiguous and
unwraps to its one string, regardless of key name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review feedback (Macroscope, Cursor Bugbot): the previous implementation
used `extractJsonObject`, which pulls the first `{...}` out of surrounding
prose, so a legitimate title like `Document {"foo":"bar"} syntax` collapsed
to `bar`. Require the whole title — after stripping an optional code fence —
to be a single JSON object before unwrapping; otherwise leave it untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0adf7d8. Configure here.

Comment thread apps/server/src/textGeneration/TextGenerationUtils.ts Outdated
Review feedback (Cursor Bugbot): models often wrap titles in quotes, so a
quote-decorated envelope like `"{"title":"X"}"` failed the brace check and
then quote-stripping left the raw JSON in the sidebar — the very leak this
change fixes. `sanitizePrTitle` had the same miss and does no quote pass at
all. Strip surrounding quotes/backticks (and a code fence) inside the unwrap
before the brace check, so both sanitizers recognise decorated envelopes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/server/src/textGeneration/TextGenerationUtils.ts Outdated
Replace the brace-matching/quote-stripping heuristic with a simpler, more
robust rule: decode the value as JSON and recurse.

- decodes to a JSON string  -> recurse on the decoded string (handles quoted
  titles and JSON-string-encoded / doubly-encoded envelopes for free);
- decodes to an object with one string value -> recurse on it (any key);
- decodes to an object with several strings -> recurse on a `title` key if
  present, else give up;
- anything else (not JSON, number, array, ambiguous object) -> unchanged.

Prose is not valid JSON, so titles like `Document {"foo":"bar"} syntax` are
left intact without any special embedded-object guard. Note: a title wrapped
in a Markdown code fence is no longer unwrapped, since a fence is not JSON.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant