MarkCleaner / Field notes

ChatGPT Hidden Characters: Find Where They Entered Your Text

A character scan can tell you what is in a string. To find where it appeared, save a local snapshot at every handoff and compare each pair in order.

Answer: Keep an untouched copy of the ChatGPT draft, then save another copy after each transfer: editor, document, CMS, and published page. Compare adjacent copies for Unicode code-point changes. The first handoff where a character appears or disappears narrows down the transformation stage. It does not identify who caused it or prove that the character is a ChatGPT watermark.

Trace the handoffs, not just the final text

If you inspect only the final passage, a scanner cannot show whether a character was in the original draft or entered later. A useful troubleshooting record is a short chain of snapshots. Give every snapshot a stage name, keep the text local, and compare the output of one stage with the input of the next.

SnapshotWhat to saveQuestion it answers
1. DraftThe exact text before leaving the source chatWas the code point already present in this copy?
2. EditorThe text after pasting into the writing appDid the paste or editor change the character counts?
3. CMSThe text copied back from the CMS field or previewDid the CMS input, formatter, or save step change the string?
4. Published pageThe text copied from the rendered pageDid publication or rendered content differ from the saved CMS version?

This is a synthetic example. The non-breaking space is deliberately written as an escape sequence so the sample itself is easy to inspect:

const stages = [
  ["Draft", "A B"],
  ["Editor", "A\u00A0B"],
  ["CMS", "A\u00A0B"],
  ["Published copy", "A B"]
];

The first comparison reports a swap from U+0020 SPACE to U+00A0 NO-BREAK SPACE at the editor handoff. The CMS snapshot matches the editor snapshot. The published copy changes back to an ordinary space. This example shows how the method reads; it is not a measurement of any real app.

Compare each adjacent pair locally

The short script below counts Unicode code points in each snapshot and reports count changes for the next stage. Paste only text you are allowed to inspect. It makes no network requests and does not modify the snapshots.

function codePointCounts(text) {
  const counts = new Map();
  for (const character of text) {
    const point = "U+" + character.codePointAt(0)
      .toString(16).toUpperCase().padStart(4, "0");
    counts.set(point, (counts.get(point) || 0) + 1);
  }
  return counts;
}

for (let i = 1; i < stages.length; i++) {
  const before = codePointCounts(stages[i - 1][1]);
  const after = codePointCounts(stages[i][1]);
  const points = new Set([...before.keys(), ...after.keys()]);
  const changes = [...points]
    .map(point => ({
      point,
      before: before.get(point) || 0,
      after: after.get(point) || 0
    }))
    .filter(row => row.before !== row.after);
  console.log(stages[i][0], changes);
}

JavaScript for...of string iteration yields code points, keeping a supplementary character such as many emoji together as one code point. A code point is not always one user-perceived character: joined emoji and combining sequences can contain several code points. This count report also misses a reorder when each code point's total stays the same. If order matters, compare the full snapshots as well.

Read the result as a stage clue

Keep the original and each stage copy until you understand the change. If cleanup is needed, make a separate output and check it in the final destination. That preserves a before-and-after record instead of erasing the clue.

Important limit: A code-point change identifies a difference between two saved strings, not the person, product, or reason behind it. Unicode assigns layout or joining behavior to characters often described as invisible. For example, U+00A0 prevents a normal line break, and U+200D can participate in joining behavior. Do not delete a character solely because its name includes “zero width,” and do not treat a scan as proof of authorship, watermarking, or an AI detector's result.

Sources and scope

This guide is a local troubleshooting method for comparing copies you control. The code and sample changes are illustrative, not user data or product test results.