No terminal renders Hebrew correctly. Here is what it took to fix it.
Half the world's terminal emulators have no bidirectional text engine at all, and the ones that do disagree about what a paragraph is. This is a field report from a year of making an AI coding agent readable in Hebrew, Arabic and Persian: what breaks, why the obvious fixes are wrong, one measured result that killed our favourite idea, and the two stupid bugs that cost us the most.
- The thing that actually breaks
- Why terminals are structurally bad at this
- Which terminals work, tested
- Why an AI agent makes it worse
- What we built, and one thing we refused to do
- The display stack rabbit hole
- A measured negative result: the Hebrew tax
- The two dumbest bugs, which cost the most
- What we still want from upstream
1. The thing that actually breaks
Type a sentence that mixes Hebrew and English into most terminals and the words survive, but their order does not. A file path lands on the wrong side of the sentence. A version number jumps to the far edge of the line. Parentheses point the wrong way. Nothing is corrupted, every character is intact, and the line is still unreadable.
This is not a font problem and it is not an encoding problem. It is the Unicode bidirectional algorithm, and whether your terminal implements it.
2. Why terminals are structurally bad at this
A browser lays out a paragraph. It knows where the paragraph starts and ends, so it can run the full bidirectional algorithm over the whole thing, work out the nesting levels, and reorder runs of text before painting.
A terminal emulator is a grid of cells. Row 24, column 60 holds one character. The program writing to it moves a cursor around and paints cells, sometimes repainting the same row many times per second. There is no paragraph. There is barely a line, because the application can rewrite half of it a moment later.
So the terminal has two bad options. Either it ignores direction entirely and paints characters in the order they arrive, which is what most do, or it guesses a paragraph boundary and reorders, which breaks the moment an application assumes it can address cells by column. Terminal emulator authors have mostly chosen the first, and they are not being lazy: doing this correctly conflicts with what a terminal is.
3. Which terminals work, tested
We tested these against mixed Hebrew and English output rather than trusting documentation.
| Terminal | Bidirectional text | Verdict |
|---|---|---|
| Konsole (KDE) | Reorders, and honours a right-to-left mark at position 0 to set line direction | usable |
| Apple Terminal | None at all | broken |
| iTerm2 | Mirrors Hebrew, reversing letter order inside words | broken |
| WezTerm | Shaping present but incorrect for this case | broken |
| Windows Terminal | No paragraph reordering | broken |
The practical consequence is uncomfortable and worth stating plainly: on macOS today there is no native terminal that renders mixed Hebrew and English correctly. We shipped macOS support, then withdrew it, because no amount of work on our side fixes a host that will not reorder. That is why the tool we ended up building is Windows and Linux only, and why Mac users get pointed at a different setup instead of a broken one.
4. Why an AI agent makes it worse
A shell prints a line and moves on. An AI coding agent runs a full text user interface: spinners, progress, collapsible trees, a status line that repaints continuously. Every repaint is another chance to get direction wrong, and any mistake is visible while you are reading.
One detail did more damage than the rest. Claude Code prefixes each assistant message with a bullet character. A bullet is directionally neutral, and Konsole's heuristic treats the first non-whitespace character of a line as the anchor for that line's direction. A neutral character anchors left-to-right. So every Hebrew reply had its first line stuck in the wrong direction while the rest of the message was fine. One character, at position 0, breaking the most visible line of every answer.
5. What we built, and one thing we refused to do
The fix is a wrapper. Claude Code runs inside a pseudo terminal, its output stream passes through a transform before reaching the emulator, and the transform injects Unicode direction control characters:
- Right-to-left runs get wrapped in an embedding pair, so a Hebrew phrase inside an English sentence keeps its own direction without dragging the rest of the line with it.
- A right-to-left mark is injected at position 0 of any line whose first strong character is right-to-left, which is what fixes the bullet-prefixed first line described above.
The interesting part is what the transform deliberately does not do. It never substitutes characters. Browser-layer right-to-left extensions often swap arrows, turning a right arrow into a left arrow inside a Hebrew paragraph. For prose in a document that is correct behaviour. In a terminal it is destructive, because the tree renderers, box drawing and status indicators of a text user interface rely on the exact glyph they emitted. Direction comes from direction marks only, never from swapping characters. There is a comment in our injector telling the next person not to add a substitution table, because it looks like an obvious improvement and it is not.
6. The display stack rabbit hole
Getting the right terminal onto Windows means running it under the Linux subsystem, and that is where a second class of problem lives. A sequence of findings, each of which looked like a different bug:
- Newer Konsole builds default to Wayland. The window management tools everyone reaches for are X11 tools, so they cannot see or manage a Wayland window. Symptom: the terminal exists in the taskbar but never appears, and no error is printed anywhere.
- Keyboard layout switching silently dies for the same reason. Live layout switching, which is how a Hebrew speaker actually types, works only when the terminal is an X11 client. Forcing the Qt platform back to X11 fixes the invisible window and the keyboard in one change. We verified this twice from a clean state, because the first result was a false positive from leftover state.
- Konsole forks by default, so the process you launched hands the window to a daemon and the window you carefully sized and raised is replaced by an unmanaged one. Launch it without forking and track it by process id.
- The layout must be applied at the right moment, not just correctly. Applying it and then sleeping two seconds leaves a dead zone where the window's focus grab has already dropped the layout, so the first few keypresses do nothing and then it mysteriously starts working. Apply immediately, then re-apply only if a query shows it was dropped, and never re-apply once it is loaded, because that yanks a typing user back to the first layout.
None of this is Hebrew-specific. It is what it costs to put a specific terminal emulator in front of a user who just wants to type in their own language.
7. A measured negative result: the Hebrew tax
Hebrew is expensive. The same answer costs roughly 31% more output tokens in Hebrew than in English, because of how the tokenizer splits the script. For anyone on a usage limit that is a real tax on working in their own language.
So we built an optimizer: strip diacritics and invisible marks from prompts, collapse whitespace while leaving code blocks untouched, and wrap the request in a short English frame, keeping the user's words verbatim.
Then we measured it, paired A/B, on two models including extended thinking at maximum effort. The net effect on output tokens was statistically indistinguishable from zero. On one configuration it came out at minus 0.5%, with a confidence interval from minus 5.8% to plus 4.8%, while the English frame adds input tokens.
The reason is structural, and obvious in hindsight. The cost lives in the output tokenizer, the answer has to stay in Hebrew because that is the entire point, and the model already reasons efficiently regardless of the prompt's language. There was nothing for the lever to grab. We shipped it off by default and wrote up the negative result instead of quietly deleting it.
8. The two dumbest bugs, which cost the most
After all of the above, the two bugs that hurt real users most were not about bidirectional text at all.
A codepage. The folder picker wrote the chosen project path to a small file as UTF-8. The launcher script read it back with the console's default codepage, which on a Hebrew Windows is 862, not UTF-8. Any project folder with a Hebrew name came back as mojibake, failed its existence check, and the launcher fell back to the home directory. The user picked a folder and the tool opened somewhere else, with no error. One missing line fixed it.
A trailing backslash. On Windows, a backslash immediately before a closing quote escapes that quote. So passing a folder that ends in a backslash, which is exactly what a drive root looks like, collapses the rest of the command line into the argument. We measured it: a child process received one argument instead of three. The terminal then reported that it could not find a path, and the agent never started. The fix is to strip the trailing separator and restore a bare drive letter as a real directory.
Both had been shipping for months behind the interesting problems. If you build tooling for other people's machines, the boring path handling deserves the same suspicion as the algorithm.
9. What we still want from upstream
Everything above is a workaround. The real fixes belong in two places: terminal emulators growing a bidirectional engine that survives cell addressing, and text user interface frameworks not assuming that column order equals reading order. Until then, right-to-left users get a wrapper, and the wrapper has to be maintained against every upstream repaint change.
If you read Hebrew, Arabic, Persian or Urdu and you have given up on the terminal, it is worth another look. If you maintain a terminal emulator, the test is one line of mixed text, and it takes ten seconds to find out where you stand.
The tools
Both are free, open source, and the whole wrapper is readable in a single directory.
Kivun Terminal, right-to-left Launchpad CLI, left-to-right
Windows and Linux. If it saves you an afternoon, a star on the repo is how other people find it.
- The bidirectional wrapper, including the comment asking you not to add character substitution: kivun-claude-bidi
- The Hebrew tax measurement and why the optimizer ships disabled: same repository, pull requests 84 and 102
- Hebrew version of the practical fix guide: Claude בעברית הפוכה