How to remove hidden Unicode characters once you've found them
Finding a zero-width space or a bidi-override character is only step one. Here is what actually strips them cleanly, what silently fails to, and how to stop them coming back.
Short, isolated findings: retype the line
For a short span flagged by a scan, select the visible text around it, delete the whole span, and retype it by hand rather than editing around the gap. Because the hidden character has no visible glyph, copying the same-looking text from elsewhere in the file risks carrying the hidden character along with it — retyping guarantees a clean insertion.
A visual find-and-replace usually can't do this
A plain text editor's find box typically has nowhere to put an invisible character as the search term — there's nothing to type or paste into it that reliably represents one. This only works if the editor supports searching by Unicode codepoint or a regex character class, which most general-purpose editors don't expose directly.
The command line, once you have the codepoint
This is where a scan's output becomes directly actionable: with the exact codepoint in hand, reference it by its UTF-8 byte sequence or a Unicode escape instead of by pasting the invisible character itself.
sed -i "s/\xe2\x80\x8b//g" file.md removes every zero-width space (U+200B). Repeat per distinct codepoint a scan reports — which is exactly why a scan that lists every codepoint present, not just "something was found," is what makes this a fix rather than a guess.
Git history is a separate problem
An ordinary commit removes the character from the current file, but any earlier commit that still contains it stays in the repository's history and is fetchable by anyone who clones it. If the hidden text was sensitive, or the concern is supply-chain provenance rather than just the live file, that needs a history rewrite and force-push on top of the content fix — not the content fix alone.
Stopping it from recurring
The common entry point is a copy-paste from something external — a dependency's README, a PR description, a chat transcript. Re-scan text from an external source before trusting it, the same way you'd review a diff before merging it.
Common questions
Can I just retype the affected line?
Yes, for a short, isolated finding this is the most reliable fix: select the visible text around it, delete the whole span, and retype it by hand. Because the hidden characters have no visible glyph, copy-pasting the same-looking text from elsewhere in the same file risks carrying the hidden character along with it — retyping guarantees a clean insertion.
Will a plain text editor's find-and-replace remove them?
Only if it supports searching by Unicode codepoint or a regex character class, and you know which codepoint you're removing — which is exactly what a scan gives you. A visual find-and-replace box usually can't be given an invisible character as the search term at all, since there's nothing to type or paste into it that reliably represents one.
Does a command-line tool like sed or grep handle this safely?
Yes, once you have the exact codepoint from a scan: sed -i "s/\xe2\x80\x8b//g" removes every zero-width space (U+200B) from a file, referencing the character by its UTF-8 byte sequence or a \u escape rather than by pasting it, which sidesteps the invisibility problem entirely. Repeat per codepoint found; a scan that lists every distinct codepoint present is what makes this safe rather than a guess.
Does committing the fix to git actually remove it from history?
Not on its own. An ordinary commit removes the character from the current file, but any earlier commit that still contains it remains in the repository's history and is fetchable by anyone who clones it. If the hidden text was sensitive or the concern is supply-chain provenance, that needs a history rewrite (and force-push) on top of the content fix, not the content fix alone.
How do I stop it from happening again?
Re-scan a file after any copy-paste from an external source — a dependency's README, a PR description, a chat transcript — before trusting it, since that is the common entry point. A few editors and linters can flag specific invisible-character classes automatically; where that exists, enabling it closes the gap this tool otherwise has to catch by hand each time.