The toggle did not stop the pack. “Destroyed” is still a vendor claim.
You open a coding agent. You are logged in. Somewhere under ~/.zcode a baseline archive appears: workspace tar, encrypted, hundreds of megabytes when the project is big. Inside the manifest, most of the weight is .git – objects, LFS cache, reflogs – plus a little global config bundled for the ride. Upload attempts sit in a queue. Failure counters climb when the blob is over the size limit.
Nobody asked for a cloud mirror of the whole history in the chat box. The privacy page talked about files you put in a conversation. This is a different shape: capture and upload as a background path, not as something you pasted.
That is not a geopolitics essay. It is a record problem.
What ferstar measured (18 Sep 2026)
ferstar, a paying ZCode subscriber, published a write-up after noticing ~/.zcode eating disk. The agent is ZCode (Zhipu / Z.ai desktop coding agent). Attribution below is to that post and the linked HN thread – we are not re-running the capture.
On a commercial workspace of about 345 MB (excluding the usual dependency junk), the client produced a ~313 MB encrypted baseline pack. Status stayed local pending with failureCount 564. OpenWrt traffic records on ferstar’s network matched that story: those chunks did not leave as a successful OSS put. Pending is not accepted.
A second, tiny public-repo workspace did leave the machine: 538 files, about 15 KB after compress and encrypt, accepted by the server. So “did anything leave at all?” – yes, at least that one. Reproductions on other machines were reported in the community thread.
Scope of the pack, from ferstar’s plaintext manifest on a sample: most of the bytes were .git (objects, LFS, reflogs), not just the current working tree. That matters because history holds deleted secrets, unpushed branch names, and remote URLs you never meant for a third-party object store.
Encryption was envelope-style: AES-256-CTR for content, RSA-OAEP wrap of the content key with a public key delivered for that credential round. The matching private key lives on the server. The local .enc is not openable by the user or the client. That is a design fact about who can unwrap, not a moral slogan.
Settings did not gate the pipeline the way a user expects. “Optimize Experience” mapped to training consent. “Repo Snapshot Indexing” mapped to whether the server indexes a snapshot. Capture and the attempt to upload still ran when a JWT was present. ferstar’s read of the client: sidecar active on login; triggers included capture before prompt and wiki-update style paths. Turning the toggles off did not stop packaging.
Vendor statement the same day (18 Sep, 17:44): uploads tied to codebase indexing / Repo Wiki; cloud Wiki generation “may” upload repository data; after Wiki, data “destroyed immediately and is not stored”; issue “fixed”; open-source and third-party review promised; extra weekly quota. Later summaries of CAICT / NSFOCUS work described an emptied or deleted bucket and a client build with the snapshot path removed. ferstar’s later notes: open-source drop and pipeline purge are real containment steps; an empty bucket after 20 Sep still does not narrate what happened to objects that may have been accepted before 18 Sep.
Hold those as attributed claims and measurements. Do not flatten them into “313 MB stolen” or “nothing ever left.”
Claim versus record
“Destroyed immediately” is a vendor claim. It lives in a statement and in later audit summaries. It may be true. It is not a fact the desktop session can assert on its own.
A session fact would answer narrower questions:
- Did capture run for this workspace, and on what trigger?
- What was in scope – file count, size, instruction-set style identity for the pack (manifest hash), and whether
.gitwas included? - What did the toggles say, and did those values actually gate the action?
- Was an upload attempted? To what class of destination? Accepted, failed, or pending – and why?
- Who holds the unwrap key: local or server-only?
- If the vendor later says deleted: is there a receipt a third party can read, or only a statement?
Without those fields, two honest engineers can talk past each other. One sees a pending 313 MB file and hears “exfiltration.” One sees failureCount and hears “nothing uploaded.” One hears “destroyed” and stops. One asks what was accepted before the bucket went empty. Same incident, different stories, because the client never wrote a short, boring run record for capture and upload the way it already writes chat turns.
Silence and euphemism are expensive here too. A loud line – “pack queued, .git included, upload failed: over max size” – costs seconds. A toggle that looks like an off switch and is not costs trust.
What a run record would need after this shape of incident
You do not need a courtroom. You need fields a human can compare across two machines and across “before / after the vendor said fixed.”
At minimum, for this class of failure:
- Capture – ran or not; trigger (login sidecar, before prompt, wiki-update, other).
- Scope – manifest hash or equivalent; file count; size; whether
.git(and which parts) entered the pack. - Settings versus gate – toggle values at the time, and whether those values actually gated the action or only labeled adjacent features.
- Upload – attempt yes/no; destination class (object store); accepted / failed / pending; fail reason when failed.
- Crypto – who can unwrap (local key material vs server-only).
- Remote delete – a vendor “destroyed” line is not a client session fact unless there is an independently readable receipt. An empty bucket on a later date is a later state. It is not, by itself, a history of what was accepted earlier.
None of that “proves” the model, the vendor, or the network behaved. Models still drift. Networks still drop. The point of the record is narrower: two runs – or a run and a press statement – can be compared on what the client claimed to capture and send.
If laptop A’s record shows capture=yes, .git=included, upload=pending, fail=over_max, and laptop B’s record shows capture=yes, upload=accepted, size=15KB, you stop arguing about vibes and start arguing about which workspaces left. If the only delete story is a statement with no receipt field, you stop treating “deleted” as something the session already knew.
Same class, different axis
Last week’s AGENTS.md story was instruction load: changelog said support, the session never recorded the load. This one is capture and upload: UI said optimize / index, the pack still queued; vendor said destroyed, the client had nothing like a delete receipt to write down.
Different axis. Same habit worth keeping. Write down what the harness actually did to the workspace – scope, gate, attempt, result – before the argument moves to motives.
Same login. Different effective boundary. Write it down.