CLAUDE WATERMARK REMOVER · PRACTICAL TEST
Why does JSON.stringify still fail after cleaning a circular object value?
Every self-referencing object in our controls raises TypeError, before and after cleanup. Reusing the same child in two sibling properties serializes successfully. The failure concerns a cycle in the object graph, not the text value or repeated text.
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 |
|---|---|---|---|
| Marked value direct string / keep / invisible true | "AB"U+0041 U+200B U+0042 | "AB"Removed 1 invisible character | {"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"AB\"},\"second\":{\"text\":\"AB\"}}","plain":"{\"text\":\"AB\"}"}{"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"AB\"},\"second\":{\"text\":\"AB\"}}","plain":"{\"text\":\"AB\"}"} |
| Plain value direct string / keep / invisible true | "AB"U+0041 U+0042 | "AB"No selected invisible characters found | {"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"AB\"},\"second\":{\"text\":\"AB\"}}","plain":"{\"text\":\"AB\"}"}{"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"AB\"},\"second\":{\"text\":\"AB\"}}","plain":"{\"text\":\"AB\"}"} |
| Empty value direct string / keep / invisible true | "" | ""No selected invisible characters found | {"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"\"},\"second\":{\"text\":\"\"}}","plain":"{\"text\":\"\"}"}{"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"\"},\"second\":{\"text\":\"\"}}","plain":"{\"text\":\"\"}"} |
| Literal null spelling direct string / keep / invisible true | "null"U+006E U+0075 U+006C U+006C | "null"No selected invisible characters found | {"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"null\"},\"second\":{\"text\":\"null\"}}","plain":"{\"text\":\"null\"}"}{"cyclic":{"error":"TypeError"},"repeatedReference":"{\"first\":{\"text\":\"null\"},\"second\":{\"text\":\"null\"}}","plain":"{\"text\":\"null\"}"} |
The self link remains after a string is repaired
The observer creates a fresh object containing text and then points its self field back to that same object. JSON.stringify raises TypeError on every such branch. In the marked fixture the text value changes from A plus U+200B plus B to AB, but the cycle is intentionally identical in shape before and after. A plain object holding only text serializes successfully with the expected value. These paired outcomes localize the failure to graph structure rather than treating an unsuccessful export as evidence that the cleaner left an unwanted character.
Sharing a child does not necessarily create a cycle
A separate shared child object appears under first and second in a new parent. Both occurrences serialize as ordinary nested text objects in all four controls. The traversal can finish each child without following a self link back to an ancestor. Plain AB, empty text and the literal word null all reproduce this difference, and null remains quoted text in the successful output. The same payload at two locations is therefore not enough to explain the cyclic error. Our observer supplies no replacer, toJSON hook or custom property getter.
Choose graph handling independently of character policy
If a destination accepts object graphs, define whether cycles are rejected or represented through an explicit reference format, and retain the graph information needed by that format. Blindly discarding repeated objects can lose legitimate sibling data like the successful shared-child control. This page demonstrates the boundary without implementing a general graph serializer or testing deeply nested limits. Earlier guides cover JSON source grammar and serialization hooks; these inputs are already objects whose relationship causes the error. The homepage handles only a string. Our synthetic values establish no Claude origin or statistical-watermark removal claim.
Successful JSON still duplicates representation
The repeatedReference field contains first and second as two complete object spellings. Parsing that JSON would recover two data subobjects rather than the original shared identity; this observer records the serialization and does not perform that additional parse. That distinction matters if the retained evidence is supposed to describe relationships as well as text. The plain branch is deliberately smaller and contains no self field, making it a separate graph rather than an automatic repair of the circular input. Cleanup never removes the self link in our test.
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
ECMAScript SerializeJSONObject 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.