Zerowidth CleanerAll measured guides

CLAUDE WATERMARK REMOVER · PRACTICAL TEST

Why does TextEncoder produce a replacement character after cleanup?

Our lone surrogate survives cleaning but TextEncoder converts it to UTF-8 for U+FFFD. The original string does not round-trip. A valid emoji pair and plain ASCII do round-trip.

Tested October 11, 2026 · v24.19.0 · 4 measured runs

Open the Claude text cleaner · Full measured data

Code-point comparison for Marker and lone high surrogate

Measured inputs and outputs

These are locally constructed test strings, not evidence that Claude inserts these characters. We executed the saved homepage script snapshot with a minimal DOM harness and compared exact strings. The invisible-checkbox state and dash mode appear in each row. The observations below test this page's specific question. Destination observations run independently on the exact input and cleaned strings in the recorded Node runtime. We make no detector-score or statistical-watermark removal claim.

Before and after observations; full code points and strings are in the JSON download.
Fixture and modeInput string and code pointsOutput and cleaner statusObserved before / after
Marker and lone high surrogate
direct string / keep / invisible true
"A​\ud800B"
U+0041 U+200B U+D800 U+0042
"A\ud800B"
Removed 1 invisible character
{"wellFormed":false,"hex":"41e2808befbfbd42","decoded":"A​�B","roundTripEqual":false}
{"wellFormed":false,"hex":"41efbfbd42","decoded":"A�B","roundTripEqual":false}
Lone low surrogate
direct string / keep / invisible true
"A\udc00B"
U+0041 U+DC00 U+0042
"A\udc00B"
No selected invisible characters found
{"wellFormed":false,"hex":"41efbfbd42","decoded":"A�B","roundTripEqual":false}
{"wellFormed":false,"hex":"41efbfbd42","decoded":"A�B","roundTripEqual":false}
Valid surrogate pair
direct string / keep / invisible true
"A😀B"
U+0041 U+1F600 U+0042
"A😀B"
No selected invisible characters found
{"wellFormed":true,"hex":"41f09f988042","decoded":"A😀B","roundTripEqual":true}
{"wellFormed":true,"hex":"41f09f988042","decoded":"A😀B","roundTripEqual":true}
Plain reference
direct string / keep / invisible true
"AB"
U+0041 U+0042
"AB"
No selected invisible characters found
{"wellFormed":true,"hex":"4142","decoded":"AB","roundTripEqual":true}
{"wellFormed":true,"hex":"4142","decoded":"AB","roundTripEqual":true}

The replacement appears during encoding

The first fixture contains A, a selected U+200B, an unmatched high surrogate and B. Cleaning deletes the marker but retains the surrogate. TextEncoder generates efbfbd for that retained unit, and decoding the produced bytes yields A, U+FFFD and B. Exact equality with the input is false both before and after cleanup. The unmatched low-surrogate reference has the same encoding loss without a marker. By contrast, the valid smile pair remains well formed and round-trips through UTF-8 unchanged; plain AB does too. We record the hexadecimal bytes alongside the resulting string so the replacement cannot be mistaken for a glyph-only display issue.

This is a separate outbound conversion boundary

Our observer uses TextEncoder, TextDecoder and String.isWellFormed on locally authored JavaScript strings. It does not load a file or recover damaged bytes. The prior encodeURIComponent guide demonstrates that its encoder throws on an unmatched surrogate; this API instead supplies replacement bytes. The earlier U+FFFD article begins with an already present replacement character. Here the input initially contains a lone UTF-16 unit, and the recorded conversion creates the replacement. That distinction determines where information is lost. We do not claim that Claude generated malformed strings or that this fixture can arrive through every browser input control without an earlier conversion.

Check well-formedness before demanding an exact export

Preserve the original JavaScript string and inspect whether it is well formed before a UTF-8 roundtrip. When exact equality is required, reject or deliberately handle lone surrogates according to the producer and destination contracts. A successful invisible-character cleanup does not make them valid scalar values. Compare code points, bytes and equality at each stage instead of deleting the visible replacement afterward and calling the export repaired. The four cases delimit behavior in the saved Node runtime only. They do not identify the source of a malformed unit, restore its intended character or certify a lossless transformation for a different serializer or transport.

Reproduce this test

Save reproduce.cjs and tested-app.js in the same folder. Run the command below with Node.js. The harness prints its runtime, script SHA-256 and every measured row. Compare those rows with the original record. Using a newer script or runtime creates a new experiment; retain the version information with your rerun.

node reproduce.cjs

Reference and next check

WHATWG Encoding Standard provides the relevant primary definition. The table and fixture analysis are original measurements. For broader inspection, use our Unicode inspector. Read the scope distinction before interpreting cleanup as a watermark result.