CLAUDE WATERMARK REMOVER · PRACTICAL TEST
Why does JSON still fail after an invisible marker beside a tab is removed?
Our quoted raw-tab fixture remains a SyntaxError after U+200B is removed. A written backslash-t escape parses successfully, while a raw tab outside the quoted string is accepted as whitespace. Position and source spelling both matter.
Open the Claude text cleaner · Full measured data
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.
| Fixture and mode | Input string and code points | Output and cleaner status | Observed before / after |
|---|---|---|---|
| Marker beside raw tab direct string / keep / invisible true | "\"A\tB\""U+0022 U+0041 U+200B U+0009 U+0042 U+0022 | "\"A\tB\""Removed 1 invisible character | {"accepted":false,"errorName":"SyntaxError"}{"accepted":false,"errorName":"SyntaxError"} |
| Raw line feed direct string / keep / invisible true | "\"A\nB\""U+0022 U+0041 U+000A U+0042 U+0022 | "\"A\nB\""No selected invisible characters found | {"accepted":false,"errorName":"SyntaxError"}{"accepted":false,"errorName":"SyntaxError"} |
| Escaped tab direct string / keep / invisible true | "\"A\\tB\""U+0022 U+0041 U+005C U+0074 U+0042 U+0022 | "\"A\\tB\""No selected invisible characters found | {"accepted":true,"value":"A\tB","reserialized":"\"A\\tB\"","codepoints":["41","9","42"]}{"accepted":true,"value":"A\tB","reserialized":"\"A\\tB\"","codepoints":["41","9","42"]} |
| Raw tab outside string direct string / keep / invisible true | "\t\"AB\""U+0009 U+0022 U+0041 U+0042 U+0022 | "\t\"AB\""No selected invisible characters found | {"accepted":true,"value":"AB","reserialized":"\"AB\"","codepoints":["41","42"]}{"accepted":true,"value":"AB","reserialized":"\"AB\"","codepoints":["41","42"]} |
Removing a selected marker leaves the raw control in place
The first authored JSON document is a quoted string containing A, U+200B, a real tab and B. JSON.parse rejects it before cleanup. The saved homepage callback removes only the selected U+200B, leaving a real tab between the letters, and JSON.parse still rejects the document. The raw-line-feed control also fails in both branches even though the cleaner reports no selected character. We catch SyntaxError by name rather than depending on runtime-specific diagnostic wording. These rows provide a reproducible reason a successful removal count cannot guarantee a valid JSON request body: another retained source character can violate the grammar in the position where it appears.
The escape and structural controls isolate the grammar boundary
The escaped-tab control contains the two written source characters backslash and t inside the quoted document. Parsing succeeds and creates a value containing an actual tab; JSON.stringify then produces the escaped representation again. The final control puts a raw tab before the opening quote of AB. It parses successfully as external whitespace and yields AB without a tab in its value. Thus a tab can be accepted as structural whitespace or represented as string data through an escape, while the unescaped occurrence inside quotes is rejected in our first row. Earlier guides cover structural whitespace and Unicode escapes that introduce U+200B after parsing. This matrix instead contrasts a retained control inside a string with its properly escaped source spelling.
Repair through a value-aware serialization boundary
Preserve the original document and locate whether the offending control is inside a quoted value or between tokens before editing. When constructing JSON from an application string value, serialize it with the destination JSON library so control characters receive the required source representation. Do not remove every tab globally: a tab in a parsed value may be intentional data, and an external tab may already be allowed whitespace. Our test parses small local documents and does not validate a schema, contact an API endpoint or inspect an actual Claude response. The public code-point records separate the source, the cleaned source and accepted values. They demonstrate grammar and representation boundaries without claiming a universal JSON repair tool or a watermark-removal result.
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.cjsReference and next check
RFC 8259 strings 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.