Zerowidth CleanerAll measured guides

CLAUDE WATERMARK REMOVER · PRACTICAL TEST

Why does URLSearchParams fail to find q when given a complete cleaned URL?

In our complete-URL fixtures, direct URLSearchParams construction stores https://example.invalid/?q as the key and get("q") returns null. new URL(text).searchParams finds q. Query-only inputs work directly. No request is sent.

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

Open the Claude text cleaner · Full measured data

Code-point comparison for Marked URL

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 URL
direct string / keep / invisible true
"https://example.invalid/?q=A​B"
U+0068 U+0074 U+0074 U+0070 U+0073 U+003A U+002F U+002F U+0065 U+0078 U+0061 U+006D U+0070 U+006C U+0065 U+002E U+0069 U+006E U+00 …
"https://example.invalid/?q=AB"
Removed 1 invisible character
{"directPairs":[["https://example.invalid/?q","A​B"]],"directQ":null,"viaURL":{"q":"A​B"}}
{"directPairs":[["https://example.invalid/?q","AB"]],"directQ":null,"viaURL":{"q":"AB"}}
Full URL
direct string / keep / invisible true
"https://example.invalid/?q=AB"
U+0068 U+0074 U+0074 U+0070 U+0073 U+003A U+002F U+002F U+0065 U+0078 U+0061 U+006D U+0070 U+006C U+0065 U+002E U+0069 U+006E U+00 …
"https://example.invalid/?q=AB"
No selected invisible characters found
{"directPairs":[["https://example.invalid/?q","AB"]],"directQ":null,"viaURL":{"q":"AB"}}
{"directPairs":[["https://example.invalid/?q","AB"]],"directQ":null,"viaURL":{"q":"AB"}}
Query with question mark
direct string / keep / invisible true
"?q=AB"
U+003F U+0071 U+003D U+0041 U+0042
"?q=AB"
No selected invisible characters found
{"directPairs":[["q","AB"]],"directQ":"AB","viaURL":{"errorName":"TypeError"}}
{"directPairs":[["q","AB"]],"directQ":"AB","viaURL":{"errorName":"TypeError"}}
Query only
direct string / keep / invisible true
"q=AB"
U+0071 U+003D U+0041 U+0042
"q=AB"
No selected invisible characters found
{"directPairs":[["q","AB"]],"directQ":"AB","viaURL":{"errorName":"TypeError"}}
{"directPairs":[["q","AB"]],"directQ":"AB","viaURL":{"errorName":"TypeError"}}

The complete address becomes part of the key

Our first fixture is a complete reserved-host URL whose q value contains U+200B. Constructing URLSearchParams directly from that text produces one pair, with the entire address prefix through the question mark included in its key. Looking up just q therefore returns null. Independently constructing URL from the same string and reading its searchParams retrieves the marked value. After cleanup the value becomes AB through both construction paths, but the direct-constructor key still contains the address prefix, so its q lookup still fails. The plain complete-URL reference confirms that this key mismatch occurs even with no selected character to remove. Full decoded pairs are saved for inspection.

A leading question mark is different from an address prefix

The ?q=AB control and the q=AB control both produce the simple q key when passed directly to URLSearchParams. Supplying either one to URL without a base throws TypeError in this observer because those controls are not complete absolute addresses. We catch and label that exception rather than pretending that one constructor is correct for every input shape. Earlier experiments compare records with sequences of key/value pairs and test replacement of repeated query keys. No record, array-valued property, append, sort or set operation participates here. This page isolates the meaning of the string supplied at construction and the difference between extracting a query and treating all supplied text as query notation.

Identify the input shape before choosing the parser

When a user pastes an absolute address, parse it as an address before accessing its query parameters. When the application already supplies only a query component, use the query constructor according to that contract. If relative addresses are allowed, define their base separately; our four controls do not choose one. Inspect the actual key list when a lookup unexpectedly returns null because an accepted construction does not guarantee the intended key was created. All observations use example.invalid and are local parsing operations, with no navigation or remote server involved. Cleanup removes the selected literal marker from one value but cannot transform the caller’s parser choice into a different input contract or certify a successful network destination.

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

WHATWG URLSearchParams constructor 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.