Zerowidth CleanerAll measured guides

CLAUDE WATERMARK REMOVER · PRACTICAL TEST

Why does joining with a pipe still create commas and lose null?

Our nested array becomes X,Y even when the outer separator is a pipe. Null becomes an empty field, and splitting the joined text cannot recover the original nested array. Cleaning the first label does not define a reversible serialization.

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

Open the Claude text cleaner · Full measured data

Code-point comparison for Marked label

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
Marked label
direct string / keep / invisible true
"A​B"
U+0041 U+200B U+0042
"AB"
Removed 1 invisible character
{"joined":"A​B|X,Y|","splitBack":["A​B","X,Y",""],"json":"[\"A​B\",[\"X\",\"Y\"],null]","nestedIsArray":true}
{"joined":"AB|X,Y|","splitBack":["AB","X,Y",""],"json":"[\"AB\",[\"X\",\"Y\"],null]","nestedIsArray":true}
Plain label
direct string / keep / invisible true
"AB"
U+0041 U+0042
"AB"
No selected invisible characters found
{"joined":"AB|X,Y|","splitBack":["AB","X,Y",""],"json":"[\"AB\",[\"X\",\"Y\"],null]","nestedIsArray":true}
{"joined":"AB|X,Y|","splitBack":["AB","X,Y",""],"json":"[\"AB\",[\"X\",\"Y\"],null]","nestedIsArray":true}
Comma label
direct string / keep / invisible true
"A,B"
U+0041 U+002C U+0042
"A,B"
No selected invisible characters found
{"joined":"A,B|X,Y|","splitBack":["A,B","X,Y",""],"json":"[\"A,B\",[\"X\",\"Y\"],null]","nestedIsArray":true}
{"joined":"A,B|X,Y|","splitBack":["A,B","X,Y",""],"json":"[\"A,B\",[\"X\",\"Y\"],null]","nestedIsArray":true}
Empty label
direct string / keep / invisible true
""
""
No selected invisible characters found
{"joined":"|X,Y|","splitBack":["","X,Y",""],"json":"[\"\",[\"X\",\"Y\"],null]","nestedIsArray":true}
{"joined":"|X,Y|","splitBack":["","X,Y",""],"json":"[\"\",[\"X\",\"Y\"],null]","nestedIsArray":true}

The outer separator does not control every generated character

We construct the same three-element array for each observation: the supplied label, an inner array containing X and Y, and null. Joining with a pipe produces the label followed by X,Y and an empty final field. Cleaning the marked label changes only its A and B boundary. The plain AB control has the same generated comma and final separator before any edit. A comma-bearing label remains comma-bearing, and an empty label produces an empty first field as well. Splitting the joined result on pipes returns strings; it does not recreate the inner array or null. Our JSON comparison and nestedIsArray flag preserve the structure that the joined display does not.

Loss happens during coercion rather than in the cleaner

The inner array remains an array in memory after joining; its string representation contributes the comma-separated X,Y portion. The null element contributes an empty field under this operation. Those observations differ from CSV quoting rules or a parser interpreting delimiters in an imported file. We call no CSV library and make no claim that the resulting pipe text follows a transport standard. The earlier split-capture page studies what a regex returns when consuming separators. This page starts with structured values before conversion to text. We do not test circular arrays, undefined elements, sparse slots, custom toString methods or labels containing the chosen pipe separator, all of which require their own contracts.

Use a representation that carries the types you need

If nested arrays and null must survive an export, choose an explicit serialization and verify its receiving parser. The JSON field here demonstrates one preserved local structure for these ordinary values; it is not a universal comparison of every serialization format. If a flat display is all that is needed, joining can be intentional, but document that its text does not carry the original types. Keep source structure when later reversal is required. An empty first field and an empty final field may represent different original values in our controls. The reproducer retains both joined and structured versions so their differences can be inspected directly rather than blamed on an invisible character or on an assumed Claude export format.

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

ECMAScript Array.prototype.join 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.