AskUserQuestion option descriptions: newlines replaced with U+FFFD since 2.1.235 (regression, last good 2.1.234)

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Aug 22, 2026

Summary

Since 2.1.235, newlines in an AskUserQuestion option description are replaced with U+FFFD () instead of rendering as separate rows. A multi-row description arrives in the terminal as a single line containing one per row break.

This is a regression: it worked correctly through 2.1.234, and there is no changelog entry for the change.

Repro

Call AskUserQuestion with an option whose description contains newlines:

{
  "label": "Merge it",
  "description": "Runs the merge from the main checkout.\n✅ Lands the fix today\n❗ Skips the local pull"
}

Expected (and the behaviour up to 2.1.234): three rendered rows.

Actual (2.1.235+): one row reading Runs the merge from the main checkout.�✅ Lands the fix today�❗ Skips the local pull, then soft-wrapped by the terminal.

Bisect

Testing each native build for a String.replace of the class [\n \r U+2028 U+2029] with U+FFFD:

| Version | Present |
|---|---|
| 2.1.220 | no |
| 2.1.230 | no |
| 2.1.232 | no |
| 2.1.234 | no |
| 2.1.235 | yes |
| 2.1.238 | yes |
| 2.1.239 | yes |
| 2.1.240 | yes |

2.1.234 contains no such expression anywhere in the binary. 2.1.220's only comparable sanitizer targets bidi controls (U+061C, U+202A–U+202E, U+2066–U+2069) and leaves newlines alone.

Where it happens

In 2.1.240 the sanitizer is a one-line helper that replaces each of \n, \r, U+2028 and U+2029 with U+FFFD. It has ~94 call sites — it looks like a general-purpose "keep this on one line" utility. Exactly one of them lands on the question dialog's option description:

description: S_(Jfy.displayDescription)

Nothing downstream splits U+FFFD back into rows, so the flattening is terminal.

The upstream pipeline that produces displayDescription preserves newlines deliberately — codepoint 10 is explicitly exempted from the control-character sanitizer it passes through. The newline therefore survives every other stage and is destroyed only at this final mapping.

Why this looks unintended rather than a deliberate restriction

The same dialog still supports multi-line question text: the question is rendered with a needsGutter flag computed by testing the sanitized text for a newline. Multi-line content is a supported concept in this component — it is only the option description that is forced to one line, and only since 2.1.235.

If flattening is intended, a plain space would be a better replacement than U+FFFD, which renders as a corruption glyph and reads to users as a broken client.

Impact

Anything composing structured option descriptions. A common convention is a short meaning clause plus a pro row and a con row, each on its own line — that now renders as one line carrying two .

Remote Control / the mobile app is not affected: it renders the same payload with the row breaks intact. So the same session shows correct output on the phone and corrupted output in the terminal, which is what made this confusing to diagnose.

Environment

  • macOS (darwin arm64), native install
  • Affected: 2.1.235 → 2.1.240 (current)
  • Last good: 2.1.234

View original on GitHub ↗