●QUOTA — The 50 percent weekly usage boost for Claude Code subscribers ends August 19. One day left to front-load any heavy parallel work●PRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1●MIGRATION — The legacy Workbench and the experimental prompt tool APIs retired on August 17. Anything that depended on them now needs a hand-rolled equivalent on the Messages API●GOVERNMENT — Claude for Government is in beta. Anthropic remains the contracting and billing party, so agencies can start without a separate cloud provider relationship●PROVENANCE — Claude models released from August 2 onward embed a machine-readable watermark in generated text. It applies worldwide rather than only in the EU, with no stated effect on quality, speed, or price●CLI — v2.1.233 adds memory cgroup support for Bash tool commands on Linux, so CLAUDE_CODE_TOOL_MEMORY_LIMIT keeps a runaway build from stalling the session●QUOTA — The 50 percent weekly usage boost for Claude Code subscribers ends August 19. One day left to front-load any heavy parallel work●PRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1●MIGRATION — The legacy Workbench and the experimental prompt tool APIs retired on August 17. Anything that depended on them now needs a hand-rolled equivalent on the Messages API●GOVERNMENT — Claude for Government is in beta. Anthropic remains the contracting and billing party, so agencies can start without a separate cloud provider relationship●PROVENANCE — Claude models released from August 2 onward embed a machine-readable watermark in generated text. It applies worldwide rather than only in the EU, with no stated effect on quality, speed, or price●CLI — v2.1.233 adds memory cgroup support for Bash tool commands on Linux, so CLAUDE_CODE_TOOL_MEMORY_LIMIT keeps a runaway build from stalling the session
What to Settle Before Watermarked Claude Text Ships Inside Your App
Claude models released on or after August 2, 2026 embed watermarks in generated text and attach C2PA-signed provenance metadata to files. Here is how I redrew the line for shipped copy, plus a measured look at where image metadata disappears and a submission gate that keeps provenance reproducible.
I was lining up store listings for four apps across languages when I stopped and stared at the folder for a couple of minutes. The Japanese description was mine, written by hand. The English one was that same Japanese text put through Claude. Korean and German came from the same route. On disk they all looked identical: text I had prepared.
Then the marking news landed. Claude models released on or after August 2, 2026 weave a machine-readable watermark into generated text. The mark lives at the model level, travels through copy and paste, and may survive a certain amount of editing. Which means the English description was very likely carrying a signal that it had passed through Claude, whether or not I intended it to.
The awkward part is what that signal actually means. It says "Claude processed this," not "Claude wrote this." Translation counts. Proofreading counts. Summarizing counts. As an indie developer I had a line I was comfortable with — I decide the substance, tools help me deliver it — and this single distinction forced me to redraw it.
The two marks fail in opposite directions
Anthropic ships two mechanisms with genuinely different properties. One is hard to remove. The other comes off almost by accident.
Property
Text watermark
File provenance metadata
Where it lives
Inside the model output itself
Attached to the file (C2PA signature)
Applies to
All generated text
Supported types such as .svg, .png, .jpg
Copy and paste
Travels with the text
Not applicable
Survives editing
Often, to a degree
Frequently lost on re-save
Default failure mode
Sticks around unnoticed
Falls off when you wanted it
Short fragments
Too little signal to detect
Lost through format conversion
That asymmetry drives everything downstream. Text drifts toward shipping with a mark you never thought about. Images drift toward losing a mark you may have wanted to keep. Leave both to their defaults and you end up somewhere other than where you intended.
Anthropic is explicit about the limits: a detected mark is a signal that content may have been processed by Claude, not a full account of its provenance, and the absence of a mark proves nothing either. Their write-up is worth reading in full at How Claude marks AI-generated content.
Redraw the line around "went through," not "was written by"
My previous rule was about authorship. Anything that counts as the work itself, I make by hand. Anything that exists to deliver or promote it, I am happy to hand to a tool. Clean enough as categories go.
Watermarking does not care about authorship. It cares about processing. Feed your own Japanese to Claude for an English translation and the English is generated text. Feed your own English to Claude for a copy edit and what comes back is generated text. You wrote the substance; the artifact that ships is model output.
This was the part that ran against my expectations. People who are careful about not generating content are the ones most likely to mark copy without noticing, because translation and proofreading feel like touching up your own writing rather than producing something new.
So I swapped the test. Not "who wrote this" but "did this go through Claude." In practice it fits on one line:
Did this exact string ever come back as a Claude response?
If you took the return value as-is, it went through, even when the input was yours. If you read the response and then rewrote the line in your own words, it did not. That version of the question is easy to answer honestly and easy to verify later.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You will be able to decide which shipped strings may pass through Claude using a test based on whether text went through the model, not on whether the model wrote it
✦You will know exactly which export step strips provenance metadata from your screenshots, and how to verify survival before you submit
✦You will be able to prevent the situation where copy ships without any record of who wrote it, using a single pre-submission gate instead of memory
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Once the test was settled, the next job was policy per artifact. Everything that leaves my machine falls into four buckets, and each one has a fixed handling rule.
This is the experience. The judgment stays with a person
Listing information
Store descriptions, subtitles, keyword fields
Yes, with a record
Character limits across many locales are exactly where tooling earns its keep
Replies to people
Review responses, support answers
Drafts only, rewritten before sending
There is a real person on the other end, and templated replies read as templated
Operational copy
Release notes, update announcements, social posts
Yes
The goal is conveying facts. Speed is the value here
The column that matters most is the last one. If you only record yes or no, the version of you working late six months from now will not remember why, and the boundary will quietly move. On days when I work through thirty-plus review replies in one sitting, the pull to send a draft verbatim is real. A one-line reason gives you something to argue back with.
On the image side, I measured where the metadata dies
Provenance metadata follows C2PA and arrives cryptographically signed, which makes tampering detectable. It is a solid mechanism. The trouble is that indie screenshot pipelines rarely preserve the conditions it depends on.
Store screenshots are almost never the original file. They get resized per device class, composited into device frames, and converted for size. I checked whether metadata survives each of those steps, embedding C2PA-equivalent custom metadata into a PNG on Linux and running it through representative export operations.
from PIL import Image, PngImagePlugin# 1. Build a source image carrying provenance-style text chunksimg = Image.new("RGB", (1290, 2796), (30, 40, 60))meta = PngImagePlugin.PngInfo()meta.add_text("c2pa", '{"claim_generator":"Claude","signature":"..."}')meta.add_itxt("XML:com.adobe.xmp", "<x:xmpmeta><c2pa:manifest/></x:xmpmeta>")img.save("source.png", pnginfo=meta)def probe(path): """Report which metadata keys survived the save""" info = Image.open(path).info return sorted(k for k in info if k not in ("jfif", "jfif_density", "jfif_unit", "jfif_version"))# 2-a. Resize for a different device classImage.open("source.png").resize((1242, 2688)).save("resized.png")# 2-b. Convert to JPEG to cut file sizeImage.open("source.png").convert("RGB").save("converted.jpg", quality=90)# 2-c. Composite into a device framecanvas = Image.new("RGB", (1290, 2796), (255, 255, 255))canvas.paste(Image.open("source.png").resize((1100, 2382)), (95, 207))canvas.save("composited.png")# 2-d. Resize while explicitly carrying metadata forwardsrc = Image.open("source.png")keep = PngImagePlugin.PngInfo()for key, value in src.info.items(): if isinstance(value, str): keep.add_text(key, value)src.resize((1242, 2688)).save("resized_keep.png", pnginfo=keep)for name in ("source.png", "resized.png", "converted.jpg", "composited.png", "resized_keep.png"): print(f"{name:18} {probe(name)}")
Resize, convert, composite: metadata gone in every case. The only survivor was the run where I wrote the carry-forward explicitly. This is not a Pillow quirk so much as the default across image libraries — metadata read on open is not written on save unless you say so.
Two practical consequences follow. First, treat any image that has been through a screenshot pipeline as carrying no provenance metadata, because it almost certainly does not. Second, the reverse inference collapses too. Absent metadata is not evidence that an image avoided AI, because a stripped file and a hand-made file are indistinguishable once written to disk.
So the presence of a mark cannot serve as your record-keeping. Record-keeping has to exist separately, on your side.
Keep a provenance ledger and run it as a gate
That is what the ledger is for. The mechanism is deliberately plain: enumerate everything that ships, declare an origin for each file, and stop the submission when a declaration and the file disagree.
#!/usr/bin/env python3"""Provenance gate for shipped artifacts. Blocks undeclared files and silent human-only edits."""import hashlibimport jsonimport sysfrom pathlib import PathSHIP_ROOT = Path("ship") # only artifacts that actually reach the storeLEDGER = Path("provenance.json") # the single place a human reviewsTARGET_SUFFIXES = {".txt", ".strings", ".xml"}ORIGINS = {"human-only", "claude-assisted", "claude-generated"}def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest()def load_ledger() -> dict: if not LEDGER.exists(): return {} return json.loads(LEDGER.read_text(encoding="utf-8"))def main() -> int: ledger = load_ledger() failures, notices = [], [] for path in sorted(SHIP_ROOT.rglob("*")): if not path.is_file() or path.suffix not in TARGET_SUFFIXES: continue key = path.as_posix() entry = ledger.get(key) if entry is None: failures.append(f"UNDECLARED {key} no origin recorded in the ledger") continue if entry.get("origin") not in ORIGINS: failures.append(f"BAD_ORIGIN {key} origin={entry.get('origin')!r}") continue digest = sha256(path) if entry.get("sha256") == digest: continue # human-only asserts "this never went through Claude" - changed content needs a fresh assertion if entry["origin"] == "human-only": failures.append( f"REDECLARE {key} human-only content changed " f"({entry['sha256'][:8]} -> {digest[:8]})" ) else: notices.append(f"UPDATED {key} origin={entry['origin']} (refresh the ledger hash)") for line in notices: print(" " + line) for line in failures: print("NG " + line) if failures: print(f"\nProvenance gate: {len(failures)} unresolved. Submission halted.") return 1 print(f"\nProvenance gate: passed ({len(notices)} notices)") return 0if __name__ == "__main__": sys.exit(main())
The ledger itself is unremarkable JSON keyed by path:
{ "ship/metadata/ja-JP/description.txt": { "origin": "human-only", "sha256": "371619c6...", "note": "Product description, written by hand" }, "ship/metadata/en-US/description.txt": { "origin": "claude-assisted", "sha256": "0000...", "note": "Hand-written Japanese translated with Claude" }, "ship/ios/Localizable.strings": { "origin": "claude-generated", "sha256": "9f2b41d0..." }}
Run it with two files deliberately left out of the ledger and you get:
UPDATED ship/metadata/en-US/description.txt origin=claude-assisted (refresh the ledger hash)NG UNDECLARED ship/android/values-ja/strings.xml no origin recorded in the ledgerNG UNDECLARED ship/metadata/ja-JP/review_reply_tpl.txt no origin recorded in the ledgerProvenance gate: 2 unresolved. Submission halted.
Note that claude-assisted drift is a notice, not a failure. Generated copy is supposed to change. The only hard stop is a missing declaration. Tighten that any further and the ledger becomes an obstacle people route around, which defeats the purpose entirely.
The hash is what actually protects human-only
The rule that earns its place is the human-only check. Change a single character in that same file and re-run:
NG REDECLARE ship/metadata/ja-JP/description.txt human-only content changed (371619c6 -> 2c7abe29)
human-only is an assertion to yourself that a file never went through Claude, and the assertion only covers the content that existed when you made it. If the bytes changed, you owe yourself a fresh statement that the change was your own hand.
That looks excessive on paper, and I ran it as a warning at first. What moved it to a hard failure was an evening spent refining a description while reading Claude's suggestions in another window, when I overwrote the shipping file with the scratch buffer and did not notice until the next morning. I had no intention of pasting the response in. It ended up there anyway.
Deliberate violations are not the risk when you work alone. Mix-ups are. A hash tells you about them before submission rather than the next morning.
The ledger belongs in your local release routine before it belongs in CI. Mine runs once ahead of the Fastlane invocation, and since then "we cannot reconstruct where this came from" has stopped happening.
The false positives come from encoding and line endings
The first thing that broke once this was running was a Localizable.strings file failing with REDECLARE when nobody had touched it. The cause was encoding. iOS string files are sometimes stored as UTF-16, and a single pass through an editor or a conversion script turns them into UTF-8. Not one character changed; the bytes are completely different.
I saved the same single line three ways to confirm it:
Line endings do the same thing. Pull in a file that came from a Windows machine, get CRLF, get a different hash.
A few of those in a row and people stop reading human-only warnings altogether, which is the worst state a gate can be in. The fix was to hash normalized text rather than raw bytes:
def stable_digest(path: Path) -> str: """Hash text in a way that ignores BOM and line-ending differences""" data = path.read_bytes() if data[:2] in (b"\xff\xfe", b"\xfe\xff"): # UTF-16 BOM text = data.decode("utf-16") else: text = data.decode("utf-8-sig") # also strips a UTF-8 BOM normalized = text.replace("\r\n", "\n").rstrip("\n") return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
With that substitution all three variants above collapse to the same digest (1c5798128d73b031...). Binary assets cannot go through this path, so I branch on suffix and apply it only to text assets such as .strings and .xml.
One caveat worth stating. rstrip("\n") absorbs trailing-newline differences but leaves leading whitespace alone. It is tempting to normalize indentation away too, and I would advise against it: loosen that far and you can no longer distinguish "reformatted" from "rewritten." Reducing false positives and preserving the meaning of the assertion pull against each other, and the balance has to sit somewhere. Trailing newlines only is where I have found it comfortable in practice.
A mark is not a disclosure
One last distinction worth keeping sharp.
The marks Anthropic applies are part of Anthropic's own transparency work. If you ship a product built on Claude, what your product needs to disclose is a separate assessment, and their help center says as much: evaluate your own obligations independently.
So you cannot point at a watermark and call disclosure handled. Whether disclosure is required depends on the platform terms you distribute under, the law that applies to you, and how much of the work you actually delegated. I am not a lawyer, and where the answer is unclear I would get it checked. What I can say is that the time that check takes depends almost entirely on whether you kept records.
Which is the real payoff of the ledger. When someone asks who wrote a particular line, you answer from a record instead of from memory. For an indie developer, that is the difference between a few minutes and half a day.
Question
Can the mark answer it?
With a ledger
Did this text go through Claude?
Possibly, if detection succeeds
It is written down
Did Claude write it or only translate it?
No distinction available
The three origin values say so
Since which version has it gone through?
Unknown
Traceable through hash history
Which parts never went through?
Not provable
That is the human-only list
What to do next
Pick one directory to stand in for ship/ and move only the strings and images that actually reach the store into it. The act of gathering them is itself an audit, and you will reliably surface a handful of files whose origin you can no longer recall. I found five. That is where to start.
Watermarking turned out to be less of a constraint for me than a prompt to finish an inventory I had been postponing. Once you can state where the line falls from records rather than from memory, using the tools gets easier, not harder.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.