Zerowidth CleanerAll measured guides

CLAUDE WATERMARK REMOVER · PRACTICAL TEST

Can Object.assign change my cleaned text before throwing an error?

Yes in our fixture: text is updated, assignment to a read-only locked property throws TypeError, and the later field is never copied. A failed Object.assign is not an automatic rollback of earlier writes.

Tested October 26, 2026 · v24.22.1 · 4 measured runs

Open the Claude text cleaner · Full measured data

Code-point comparison for Marked value

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 value
direct string / keep / invisible true
"A​B"
U+0041 U+200B U+0042
"AB"
Removed 1 invisible character
{"error":"TypeError","text":"A​B","locked":"fixed","hasLater":false,"keys":["text","locked"]}
{"error":"TypeError","text":"AB","locked":"fixed","hasLater":false,"keys":["text","locked"]}
Plain value
direct string / keep / invisible true
"AB"
U+0041 U+0042
"AB"
No selected invisible characters found
{"error":"TypeError","text":"AB","locked":"fixed","hasLater":false,"keys":["text","locked"]}
{"error":"TypeError","text":"AB","locked":"fixed","hasLater":false,"keys":["text","locked"]}
Empty value
direct string / keep / invisible true
""
""
No selected invisible characters found
{"error":"TypeError","text":"","locked":"fixed","hasLater":false,"keys":["text","locked"]}
{"error":"TypeError","text":"","locked":"fixed","hasLater":false,"keys":["text","locked"]}
Retained joiner
direct string / keep / invisible true
"A‍B"
U+0041 U+200D U+0042
"A‍B"
No selected invisible characters found
{"error":"TypeError","text":"A‍B","locked":"fixed","hasLater":false,"keys":["text","locked"]}
{"error":"TypeError","text":"A‍B","locked":"fixed","hasLater":false,"keys":["text","locked"]}

The error occurs after one successful write

The target begins with text equal to old and an enumerable, non-writable locked property equal to fixed. The source order is text, locked and later. Object.assign first stores the supplied text, then throws TypeError while attempting to replace locked. The recorded target retains the new text, locked stays fixed, and hasLater is false. Recording only the exception would conceal the already completed mutation. Our keys field shows that the final object still has exactly text and locked rather than the unvisited source field.

Cleanup changes the payload but not the failure sequence

The marked input updates target.text with interrupted AB before cleanup and AB afterward. The ordinary text control reaches the same error with no selected marker. Empty text is also successfully written before the error, while the retained-joiner control preserves its joiner in the partially updated target. Every branch constructs a new target and source, avoiding state left behind by a prior failure. This makes the partial mutation reproducible without a server, shared configuration object or a real user record. The exclamation-style edits used in other guides are not involved here.

Define a commit policy before editing a live record

If a destination requires all fields to change together, an exception from a copying operation is not evidence that the record stayed unchanged. Review writable properties, setters and the intended transaction boundary before applying a cleaned payload to a shared target. Our fixture does not implement a transactional update, rollback routine or deep merge. It shows why the caller needs such a policy when partial state is unacceptable. Copying into a fresh object can have different semantics and still requires validation of the final record and its destination requirements.

Property failures differ from text failures

The cleaner neither creates the locked descriptor nor calls Object.assign. The TypeError is caused by the observer destination, while the cleaner produces its selected edit successfully. Earlier special-key coverage observes prototype assignment; this page uses ordinary text keys and a read-only data property. Earlier freeze coverage asks whether nested objects remain mutable, not whether a failed multi-property write leaves changes. All values are synthetic, and no global object is modified. These local mutation results establish no authorship, watermark removal or detector-score claim.

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 Object.assign 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.