claude-code - 💡(How to fix) Fix [BUG] Unicode BiDi isolate characters (FSI U+2068 / PDI U+2069) stripped on session resume

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

Error Messages/Logs

No error messages. The characters are silently stripped -- RTL text displays as LTR (jumbled/reversed reading order) on resume, while it displayed correctly during the original live session.

Code Example

$ grep -o $'\xe2\x81\xa8' ~/.claude/projects/.../<session-id>.jsonl | wc -l
31   # 31 FSI characters stored

$ grep -o $'\xe2\x81\xa9' ~/.claude/projects/.../<session-id>.jsonl | wc -l
31   # 31 PDI characters stored (matched pairs)

---

{
  "type": "assistant",
  "content": [{
    "text": "⁨أهم اللغات الحديثة مع مميزات وعيوب:⁩\n\n## ⁨١. TypeScript⁩\n..."
  }]
}

---

No error messages. The characters are silently stripped -- RTL text displays as LTR (jumbled/reversed reading order) on resume, while it displayed correctly during the original live session.

---

---
   name: RTL-safe
   description: Wrap RTL lines with Unicode BiDi isolates
   keep-coding-instructions: true
   ---
   
   When any line contains Arabic/Hebrew script characters, wrap that line
   with U+2068 (FSI) at the start and U+2069 (PDI) at the end.
   Do not wrap lines inside fenced code blocks.
   Skip pure English/Latin lines.

---

grep -c $'\xe2\x81\xa8' ~/.claude/projects/.../<session-id>.jsonl
   # Returns 1 (line count), confirming FSI bytes are saved
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Unicode 6.3+ BiDi isolate characters -- First Strong Isolate (U+2068) and Pop Directional Isolate (U+2069) -- are correctly emitted by the model during live streaming and render RTL text properly in the terminal. However, when a session is resumed (claude --resume or /resume), these characters are stripped from the displayed output, causing Arabic/Hebrew/RTL text to lose its right-to-left directional formatting.

The characters are saved correctly in the session JSONL file (verified via byte inspection), but the rendering pipeline drops them when replaying conversation history.

Byte evidence from stored session:

$ grep -o $'\xe2\x81\xa8' ~/.claude/projects/.../<session-id>.jsonl | wc -l
31   # 31 FSI characters stored

$ grep -o $'\xe2\x81\xa9' ~/.claude/projects/.../<session-id>.jsonl | wc -l
31   # 31 PDI characters stored (matched pairs)

Extracted JSON from storage confirms correct structure:

{
  "type": "assistant",
  "content": [{
    "text": "⁨أهم اللغات الحديثة مع مميزات وعيوب:⁩\n\n## ⁨١. TypeScript⁩\n..."
  }]
}

FSI/PDI are intact in storage, but absent from rendered output on resume.

What Should Happen?

FSI (U+2068) and PDI (U+2069) characters should survive the full round-trip: model output -> JSONL storage -> session resume/replay -> terminal display. These are valid Unicode 6.3+ formatting characters (not legacy control characters) and are the only BiDi mechanism that works in Claude Code's Ink-based terminal renderer.

During live streaming, RTL text wrapped in FSI/PDI renders correctly. The same text should render identically when the session is resumed.

Error Messages/Logs

No error messages. The characters are silently stripped -- RTL text displays as LTR (jumbled/reversed reading order) on resume, while it displayed correctly during the original live session.

Steps to Reproduce

  1. Create a custom output style at ~/.claude/output-styles/rtl-safe.md:

    ---
    name: RTL-safe
    description: Wrap RTL lines with Unicode BiDi isolates
    keep-coding-instructions: true
    ---
    
    When any line contains Arabic/Hebrew script characters, wrap that line
    with U+2068 (FSI) at the start and U+2069 (PDI) at the end.
    Do not wrap lines inside fenced code blocks.
    Skip pure English/Latin lines.
  2. Activate via /config -> Output style -> RTL-safe

  3. Start a new session: claude

  4. Ask: "Reply in Arabic: brief overview of modern programming languages"

  5. Observe: Arabic text renders correctly right-to-left during live streaming. Each line displays with proper RTL direction.

  6. Exit the session.

  7. Resume: claude --resume (select the session)

  8. Observe: The same Arabic text now displays LTR -- words in reversed order, punctuation misplaced. The FSI/PDI isolates that made it render correctly are gone from the display.

  9. Verify characters exist in storage:

    grep -c $'\xe2\x81\xa8' ~/.claude/projects/.../<session-id>.jsonl
    # Returns 1 (line count), confirming FSI bytes are saved

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.146 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Terminal/Shell

  • Other (Konsole / KDE terminal emulator)

Background context:

  • Legacy BiDi embeddings (U+202A LRE, U+202B RLE, U+202C PDF, U+200E LRM, U+200F RLM) are already stripped by Claude Code's renderer during live streaming -- this is known. Unicode 6.3+ isolates (FSI/RLI/LRI + PDI) are the modern replacement and the only ones that work.

  • A per-line FSI/PDI wrap was empirically confirmed as the only working RTL pattern in Claude Code's terminal (Ink renders via ANSI cursor positioning; the terminal emulator applies BiDi reordering per row after writes settle; isolates survive this pipeline, legacy embeddings don't).

  • This same approach was successfully implemented natively in opencode's TUI via its opentui render layer, where BiDi isolates are injected at the renderer level rather than relying on model output.

Affected scripts: Arabic, Hebrew, Urdu, Persian/Farsi, Pashto, Syriac, Thaana, NKo, Samaritan, Mandaic, and any text using Arabic/Hebrew presentation forms.

Suggestion: Preserve U+2066 (LRI), U+2067 (RLI), U+2068 (FSI), and U+2069 (PDI) through the history replay rendering path. These four characters are the complete Unicode BiDi isolate set and should be treated as valid text content, not stripped.

Longer-term, a built-in bidi setting (auto/always/never) that injects isolates at the render layer would solve both live and resume cases without requiring model cooperation or extra tokens.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Unicode BiDi isolate characters (FSI U+2068 / PDI U+2069) stripped on session resume