receipts
Proof the other model actually read it.
Anyone can claim their tool had a second agent review the code. A receipt is the artifact that settles it: one HTML file holding the whole run, written on your machine, that you can hand to a reviewer who was not there.
What it is
Add --receipt to any run and ccx writes one self-contained HTML
file when the run finishes. No stylesheet, no script, no image, no font, nothing
fetched. Open it on a plane, six months later, on a machine that never had ccx
installed, and it renders.
ccx --receipt "fix the off-by-one in sumAll"
receipt → ~/.claudecodex/receipts/<run id>.html
It is written before anything else happens at the end of a run, so a failure anywhere downstream cannot lose it. It is free and it stays free.
What it proves
That a model from a different company read this specific change and either found something or did not. Everything in the file is a thing the run actually produced, so the claim can be checked rather than taken:
- Which agent wrote and which reviewed, and how the pair decided that — including what the free local router predicted and whether the paid conference agreed with it.
- The argument itself, as a transcript, timestamped from the start of the run. The conference and the review are one conversation, so it reads in order.
- Every finding with its failing input. A finding only reaches the file if the reviewer could name inputs or state producing a wrong result, a crash, or data loss. That is why each one has a concrete case next to it rather than an adjective.
- Findings the author refused, with its stated reason. A run that ended in a standing disagreement says so, and shows both sides.
- The diff the reviewer read. Not your repository — the change, which is the only thing it was shown.
- The guard that ended the run, in the same plain words the terminal used, plus the number of agent calls spent against the cap of twenty.
- Every round, not just the last. Up to twelve rounds of history, so a fix that made things worse is visible rather than averaged away.
A clean receipt is not a certificate. It says a rival model read this diff and reported nothing that cleared the burden of proof. It does not say the code is correct, that the task got done, or that a file nobody touched still works. The limits are on the docs page and they are short.
What is in the file
The shape below is the whole file. A receipt is built by reading these fields off the run, and there is nothing in it that is not one of them.
| Field | What it holds |
|---|---|
| runId | 22 characters drawn from 128 random bits. It is the filename and, if a run is ever shared, the URL. |
| createdAt | When the run finished, as an ISO timestamp. |
| ccxVersion | The version that produced it. |
| task | What you typed, to a ceiling of 2000 characters. |
| domain | One of seventeen fixed categories the router classifies work into. Anything unrecognised becomes general. |
| conference | How the driver was chosen: rounds, elapsed time, winner, the stated reason, what the router predicted, and whether it agreed. |
| driver | Which agent wrote, its exit code, and its error if it had one. |
| reviewer | Which agent read the diff. Always the other one. |
| loop | The stop state and its sentence, the round count, the best round's finding count, whether it regressed, whether the change collapsed, the changed-line count before and after, and the files touched. |
| loop.history | Up to twelve rounds, each with the findings that round produced. |
| findings | What was still open at the end. Each is a summary, a failing scenario, a file, and a suggested fix. |
| refused | Findings the author declined, each with the reason it gave. Nothing else from the dispute record travels. |
| diff | The change the reviewer read, capped at 120KB — the reviewer never saw more than that either. |
| transcript | Up to 400 lines of the conversation, each tagged claude, codex or ccx and offset in milliseconds from the start. |
| calls | Agent calls spent, against the cap. |
What never goes in it
The list above is not filtered down from something larger. The builder reads those named fields and nothing else, and there is no place in it where the run's state is copied wholesale. A field that was never read cannot leak, which is a different and stronger guarantee than a denylist somebody has to remember to extend.
Absent by construction
- The path to your repository
- Your working directory
- The git remote and the branch name
- Your hostname and your username
- Anything from the environment
- The dispute record's internals
Two builds, one source
- The local file is yours, unredacted, written to
~/.claudecodex/receipts/with permissions for you alone. - The outbound copy is a different build of the same run with the redactor in front of it.
--receipt=-streams that one, because a file you are about to paste into a pull request is leaving the machine.
The redactor, before anything leaves
The bytes in a receipt are a source diff, which means they are also
.env values, bearer tokens, private keys, connection strings, and
whatever customer data was sitting in a fixture. Any share feature that leaks a
key once is dead forever and deserves to be. So the outbound build runs a fixed
ladder of rules, in this order, and the order matters: a known key shape carries
its own name into the report, and an entropy match does not.
- Secret files are dropped whole, never partially scrubbed. The
.envfamily in both spellings,.pem,.key,.p12,.pfx,.jks, keystores, SSH private keys,.npmrc,.netrc,.tfvars,.htpasswd, service-account JSON, and any file whose name says credentials and whose extension says data. The interesting part of a change to.envis that it happened, not what the value was. - Private key blocks are removed entire, from BEGIN to END.
- Sixteen known key shapes are matched and named — Anthropic, OpenAI, GitHub tokens and PATs, Slack, AWS access and session ids, Google API keys and OAuth tokens, Stripe, SendGrid, npm, GitLab, DigitalOcean, JWTs, and the credentials inside a connection string. Only the match is replaced, so the surrounding code stays readable and the report tells you which credential to go and rotate.
- A line assigning to a secret-ish name is replaced whole, because
AUTH_HEADER: Bearer abc123hides the credential behind an ordinary-looking first word. - Your identity comes out: repository path, home path, username, email address.
- An entropy sweep catches in-house token formats nothing above knows the shape of. The threshold is 4.05 bits per character, which sits just above the 4.0 that pure hex tops out at, so git SHAs and content hashes fall under it by construction rather than by an allowlist somebody maintains.
Redacted or nothing. The builder takes the scrubber as an argument. The local build passes a pass-through and marks the result as unredacted; the outbound build passes the real redactor; and the upload path refuses any object whose redaction record is missing. There is no code path from raw text to a socket, and that holds because of how the code is arranged rather than because somebody remembered to call the redactor.
And a person looks, every time. There is no
--share-yes. Sharing needs a terminal and a typed y on
every single run, because CI is precisely where the secrets are and a flag that
skips the eyeball is the mechanism by which a key eventually ships.
When it refuses
Over-firing is the correct direction to be wrong in, so the rules are deliberately eager and a second gate catches the result. More than 25 replacements, or more than a fifth of the diff's lines altered, and it will not publish at all. A diff that is mostly redaction marks is not a review anyone can read, and shipping it would be a worse outcome than saying no.
You can watch the whole thing run without sending anything. This prints the exact bytes an upload would carry, after redaction, with a count per rule:
ccx --share-dry "fix the off-by-one in sumAll"
would upload 41,208 bytes — nothing was sent
The byte count is whatever your run produces. A redactor you cannot audit is worth
nothing, and one you have to pay to inspect is worse, so this is free and every rule
is a literal in SECRET_FILE_RULES and TOKEN_RULES in
src/share.mjs.
The hosted page at /r
A receipt is a file, and a file is awkward to put in a pull request. The intended
answer is a link: a run at share.claudeandcodex.com/r/<id>,
rendered server-side, no JavaScript, strict content policy, listed nowhere, and
noindex, nofollow. The id is 22 unguessable characters and the link
is the only way in. Whoever holds the delete token can destroy it without an
account, and deleting removes the stored object rather than hiding the link.
Nothing has been uploaded, because nothing can be yet. The server is
written and the client's upload function exists, but no flag in the CLI reaches
it: bin/claudecodex.mjs does not import upload, and
--share-dry is deliberately the only thing on that path a user can
run. Today a receipt is a local file and that is the whole of it.
Which is also why there is no example run on this page. The one thing on this site that must never be faked is a receipt, since the entire claim is that a rival model genuinely reviewed the code. A mocked-up run with invented findings would be exactly the lie the artifact exists to make impossible. When there is a real one, it goes here, and it will be a link to a run you can check.
Illustration · not a real run · no receipt has been published
The layout of a real receipt, with every value replaced by a description of it. This is a diagram of the fields listed above, not a run.
Whether the hosted half is ever switched on is decided by a public test rather than by wanting to: the terms are on the pricing page. In the meantime, what someone who was sent a receipt sees is a page of its own.
Written against src/share.mjs, bin/claudecodex.mjs and
worker/, and correct as of 31 July 2026. If a sentence here and the code
disagree, the code is right and this is a bug:
angus@angusangus.com.