CLAUDE WATERMARK REMOVER · PRACTICAL TEST
Why can JSON.stringify throw even when my cleaned text is ordinary AB?
Our enumerable computed getter throws during JSON export for every text control. A plain object and an explicit text-only whitelist export successfully. The exception comes from accessing a computed property, independently of character cleanup.
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 | {"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"AB\"}","whitelisted":"{\"text\":\"AB\"}"}{"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"AB\"}","whitelisted":"{\"text\":\"AB\"}"} |
| Plain value direct string / keep / invisible true | "AB"U+0041 U+0042 | "AB"No selected invisible characters found | {"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"AB\"}","whitelisted":"{\"text\":\"AB\"}"}{"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"AB\"}","whitelisted":"{\"text\":\"AB\"}"} |
| Empty value direct string / keep / invisible true | "" | ""No selected invisible characters found | {"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"\"}","whitelisted":"{\"text\":\"\"}"}{"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"\"}","whitelisted":"{\"text\":\"\"}"} |
| Retained joiner direct string / keep / invisible true | "AB"U+0041 U+200D U+0042 | "AB"No selected invisible characters found | {"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"AB\"}","whitelisted":"{\"text\":\"AB\"}"}{"keys":["text","computed"],"getterCalls":1,"exported":{"error":"Error"},"plain":"{\"text\":\"AB\"}","whitelisted":"{\"text\":\"AB\"}"} |
The source value is clean while object access fails
Each observer creates an object holding the supplied text and defines an enumerable computed property whose getter deliberately throws Error. Direct JSON.stringify enters that getter once and fails, even after the marked text becomes AB. The saved keys list includes both text and computed, showing why this object is larger than a single text field. Object.keys itself lists the names without reading the getter value. We retain an explicit getter call counter so the source of the exception is measured rather than inferred from a generic failure message.
Two successful exports isolate the troublesome property
The plain branch serializes a new object containing only text. The whitelist branch serializes the original object with a property list containing only text. Both preserve the supplied string, including empty content and a retained joiner. Neither branch requests computed. Plain AB still triggers the direct error, demonstrating that selected invisible characters are unnecessary for this failure. The authored error is not a built-in rejection of the particular letters AB. We record its exception class rather than treating the application-defined message as a standard diagnostic.
Export requires a property-access policy
Inspect the object and serialization configuration when a clean field does not export. A whitelist is useful only when it deliberately selects the intended schema; it is not a general solution for every failing object. A getter on a selected field could still run and throw. This fixture has one computed property, no proxy, no asynchronous work and no external side effect. It does not demonstrate that reading arbitrary objects is harmless. The cleaner processes a string and never creates this computed getter or chooses the fields passed to JSON.stringify.
Keep this boundary separate from other JSON failures
A circular graph fails because of its relationships, while a toJSON hook can replace an exported value. These controls instead fail while the serializer requests an enumerable property value. Saving the exact object construction makes that distinction reproducible. All text was locally authored; no Claude samples or watermark tests are involved. A successful export of the text-only control establishes only that this small representation can be serialized in the recorded runtime. It does not certify the destination application or show removal of an authorship signal.
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.