[BUG] Unicode BiDi isolate characters (FSI U+2068 / PDI U+2069) stripped on session resume

Resolved 💬 1 comment Opened May 21, 2026 by Ravioooo Closed Jun 20, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] 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:

```markdown
---
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.
```

  1. Activate via /config -> Output style -> RTL-safe
  1. Start a new session: claude
  1. Ask: "Reply in Arabic: brief overview of modern programming languages"
  1. Observe: Arabic text renders correctly right-to-left during live streaming. Each line displays with proper RTL direction.
  1. Exit the session.
  1. Resume: claude --resume (select the session)
  1. 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.
  1. Verify characters exist in storage:

``shell
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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗