A coding assistant has to read code before it can complete a function or fix an error. You open a repo and assume you handed over the few files in this working tree that this task can use. If .env already sits in .gitignore, a lot of people treat that as “the assistant cannot see it, and the cloud cannot see it either.” On 18 September 2026 the developer ferstar wrote the next layer down: after you sign in, Zhipu’s official desktop client ZCode builds a workspace snapshot on this machine. The bulk of that list is not src/. It is the complete .git directory.
A previous piece covered what to redact before you paste a password into ChatGPT or Gemini: that is a string you chose to paste. This one changes the question. You never pasted .env into the chat. Do the Git object store, the LFS cache, and the reflog inside a workspace snapshot still carry keys you already deleted. If a key has to move, use a one-time link in the shape s.html?id=…#…. Create and read both work without an account. MyPassGen’s tools open without sign-up. The rest of this page does not reverse a client and does not explain how to block someone else’s upload. It lines up the numbers already written in ferstar’s 18 September 2026 write-up and the same-day ITHome restatement of the official note, plus the checkpoints directory you can open on this computer.
Split two jobs first
“I already upgraded to a build that no longer uploads” blocks the next pack. Snapshots already built, already off this machine, and that the company says are “destroyed as soon as the Wiki is generated” cannot be checked from the outside. You cannot open the server and confirm a backup is gone, or who still holds the private key. Look for a pending .enc first, then decide which passwords that ever entered Git have to rotate. Do not substitute a client version for “does this machine still hold a snapshot, and can git log still print the test key.”
Opening a repo is not handing over this tree
A function you paste into chat is the context you selected. A workspace snapshot is a different path: the client packs the repo you opened, and the range can be larger than “the files this prompt needs.” After ferstar split the list by size, .git/lfs/, .git/objects/, and .git/logs/ together were about 86.6%. Source and docs were about 13.4%. So even if this directory already deleted .env, an old blob in the object store can still ride along.
That is not the same path as “did this website send the password I am generating as business data.” On MyPassGen’s generator page you can check Network and see that the plaintext is not sent as business data. A desktop assistant that packs a snapshot uses its own local directory and its own outbound connection. A file you git rm is often “not in this tree,” not “never appeared in history.” Writing an unredacted key into a commit is the same class of problem as writing a password into an email body: the other side is looking at a copy you once left on purpose. That path is in if you put a temporary password in an email, what Sent, forwards, and phone previews still keep.
One more layer is easy to mix up. .gitignore blocks “do not track this path again.” It does not block “this path was committed once.” If the assistant only reads files in the current tree, ignore rules still help. If a snapshot packs the whole .git directory, those rules do not help. A local branch you never pushed, operations still sitting in the reflog, and an internal repo URL in .git/config all belong to the set “this editor tab cannot see it, the object store still has it.”
The numbers written on 18 September
ferstar wrote that the starting point was ~/.zcode using more than 700 MB. v2/checkpoints/ was about 303 MB. Inside it sat a .enc of about 313 MB. The status file recorded about 345 MB before the pack, 313070842 bytes after encryption, kind baseline, and a failureCount of 564. That commercial-project snapshot stayed local in pending; the write-up says it did not upload. A much smaller public repo was a different story: 538 files, about 15 KB after compression and encryption, status received by the server. So “did anything actually leave” — at least that once, the small repo did.
The same commercial-project file list: 42,411 files; .git/lfs/ about 196.1 MB (56.8%), .git/objects/ about 102.2 MB (29.6%), .git/logs/ about 0.6 MB (0.2%). A separate community reproduction wrote that on ZCode 3.12.3, one project snapshot was about 748 MiB, with .git about 98.91%. On the capture triggers, the write-up names captureBeforePrompt before a question, and repo-wiki-update when a task ends. In one active session log, snapshot capture showed up as many as 62 times.
The official note went out at 17:44 on 18 September. ITHome restated it that evening. The points: the issue sat in “code repository indexing,” used for a local index, session-checkpoint rollback, and Repo Wiki; generating a Wiki page in the cloud “may” trigger a repository-data upload; after the Wiki is generated, that uploaded data is destroyed at once and is not kept; the feature was on by default in the early launch, some users were affected, and the issue “has already been fixed”; ZCode would be open-sourced soon, with a third-party review; every user would get one extra weekly quota reset. The company did not deny that an upload happened. What stays in doubt is the upload range, whether you could turn it off at the time, and how “destroyed at once” can be checked from outside. On 20 September, InfoQ restated a letter from Taiyuan Chengming Technology to Beijing Zhipu Huazhang asking for deletion, data flow, logs, and who is accountable. That is a company-side question. It is not a destruction receipt you can open on this machine.
.git is the bulk: deleted keys still sit in objects
No .env in the current working tree only proves this checkout does not have that path. Git stores blobs by content. If a commit once wrote DATABASE_URL= or AWS_SECRET_ACCESS_KEY=, and you later edited the file, committed again, or even deleted it from this branch, the old blob often still sits in .git/objects until a gc actually drops it. An LFS cache can keep historical large files. The reflog records which branches you moved on this machine. Pack those three into a snapshot and the cloud is not holding “this screen in the editor.” It is holding the history this repo accumulated here.
The write-up also said workspace filters exclude some secret files, but .git still entered the pack. That sentence is worth a pause. Today you move .env out of the repo and leave only .env.example. This tree looks clean. Last year’s mistaken commit still sits in the object store. A filter that only looks at working-tree paths will not strip it. An internal GitLab hostname in .git/config, and a feature-branch name that never left this laptop, live in refs and the reflog. “I already gitignored it” does not pull those back.
You can compare this with “a password hash leak is not the same as someone already reading the plaintext.” A hash is a one-way store, and you still rotate. A key in Git history is often the plaintext itself. Checking this machine against a common weak-password list only proves a hit on a public list. It does not prove whether a given snapshot carried your old commit. That difference is in local leaked-password lists vs Have I Been Pwned: what each check proves. Rotate the set that once entered a commit, not the glance that says “this tree is clean now.”
Encrypted does not mean only you can decrypt
The outbound shape the write-up reconstructed is this: the client asks zcode.z.ai for snapshot-upload credentials, and gets an object key, a size limit, and an RSA public key. This machine packs the workspace as tar.gz, encrypts it with AES-256-CTR, wraps the symmetric key with that public key, and posts tar.gz.enc straight to Aliyun OSS. The private key stays in the cloud the whole time. The hundreds of megabytes of .enc on disk will not open for you, and they will not open for the client. The algorithm name says AES-256. That solves “the path and the bucket are not a plaintext file.” It does not hand the decrypt right to you.
That is the same boundary as a drive page that says “encrypted.” When the vendor holds the key, the storage side can still open the file. When you encrypt first with a passphrase you hold, the other side should see ciphertext only. The difference is not whether the four letters AES appear. It is who holds the private key or the passphrase. MyPassGen’s file encryption box does streaming AES-256-GCM in the browser, one file up to 5 GB, output .lock / .enc, and opens without an account. You send the passphrase on a different path. The file is not uploaded as business data. The envelope key on that ZCode snapshot used a server-issued public key and a server-held private key. The goal is the opposite: make sure the server can decrypt. See before you drop a file in the cloud, who can read the plaintext — and which path the passphrase should take.
The official note says the upload is destroyed at once after the Wiki is generated and is not kept. If that destruction happens on the server, you cannot check backups, object-store versions, or private-key copies from this computer. ferstar wrote that on 3.14.0 the upload-path code was gone from the client, upload-credential returned 404, and only a local checkpoint remained. That can prove “this new client no longer asks for those credentials.” It cannot prove “the small-repo snapshot the server already received is physically gone from every copy.” Reading “it was encrypted” as “only I can see it” skips the rotation you still have to do.
Turning a setting off is not stopping the pack
The write-up lined two toggles up against the code path. optimizeAgentExperienceEnabled (optimize the experience) covers whether data may be used for training. After you turn it off, the snapshot still packs. repoSnapshotIndexingEnabled (repo snapshot indexing) covers whether the server builds an index after it has the snapshot. After you turn it off, the local pack still runs. On 3.12.3, the capture-and-upload logic came up after login handed it a JWT. The UI had no separate “do not pack and upload” switch. The official note did not list a box a user can uncheck and then verify, on the spot, that outbound traffic has stopped. It said the feature was on by default early on, and that the issue was already fixed.
So “I never turned on Repo Wiki” and “I turned training off” are not a waiver for 3.12.3. Trust whether your checkpoints directory had a new .enc at the time, and whether the status was pending or received. After you upgrade to the 3.14.0 the write-up names, look at the same directory again: does a new pending pack appear, and does the credential URL still return success. The client can hot-update. The version number and that directory are two things you can look at more than once. A news headline is not.
A snapshot of the official privacy policy saved on 18 September still showed a last update of 15 June 2026. The policy said the product collects text, files, and code submitted in a conversation — the usual range for an assistant calling a model. The write-up points out that the page, at that time, never said a full-repo snapshot or a full Git history would go to the cloud. When a policy sentence and a local file list disagree, trust the list and the status file. Do not work backwards from “I agreed to the privacy policy” to “so only the paragraph in the chat box left.”
Side by side: this tree, Git history, the chat
The same test key that once entered a repo can split into at least four “who can still read it” paths. The difference is not the assistant brand. It is where a copy was made, and who holds the decrypt right.
| What you did | What this machine still keeps | Leftover the official note or the write-up already named |
|---|---|---|
| Only opened the repo, did not sign into the assistant | The working tree and .git |
None (the snapshot path had no login yet) |
Signed into 3.12.3; this tree already deleted .env |
Old blobs still in the object store | The snapshot list can still hold a full .git; a small repo had a server-received record |
| Turned off training / snapshot-index toggles | Unrelated to the object store | 3.12.3 write-up: this machine still packs; the toggles do not cover upload |
| Upgraded to a build that no longer uploads, never looked at checkpoints | Old .enc and status files may still sit here |
The next outbound stop; whether a received snapshot was destroyed cannot be checked from outside |
| The key never entered Git; the handoff used a one-time link with the channel split | Test files can be deleted | A snapshot search cannot find a complete credential; both halves are required to decrypt |
Do not mix the fifth row with the first four. If you write a full s.html?id=…#… into the repo and then open the assistant, history and a snapshot can still pick up the whole credential. The host that parks ciphertext still cannot see the key. Split the id and the key, and a full-text search cannot find a link that opens. A complete link is still a password. Encrypt-then-sync is for a file. Once Git’s object store has seen plaintext, adding a .lock later does not wipe the old blob.
Check on the spot
These steps do not depend on any brand promise. Use a test password and a throwaway repo that will not sign into a real work account and will not point at a company repo. In an empty directory, commit one line such as orange-lake-7, then delete it. Do not rehearse with a master password you still use, a production API key, or a live one-time link.
- Read the version on ZCode’s about page or the installer. The write-up names 3.12.3 as the problem build and 3.14.0 as the build that removed the upload path. Trust the number you read now. Do not substitute a group announcement for that look.
- Open
v2/checkpointsunder the local data root (on macOS and Linux that is often~/.zcode/v2/checkpoints; on Windows use the user directory after install). Is there a.enc, and a status JSON next to it. The fields you can open: whetherworkspacePathis a project you thought you never opened,encryptedSizeBytes,failureCount,kind. A matching path means this machine packed that repo. A largefailureCountonly means that pack did not go out. It does not prove no other repo ever succeeded. - Create an empty test repo, commit one test password, then delete it from this tree. Use
git log -porgit log --all --full-history -- filenameand see whether the old commit is still there. If it is, “I already deleted it” does not save the object store. That is Git’s own behaviour, with or without an assistant. If a snapshot packs.git, this is the layer it reads. - If 3.12.3 ever opened that test repo: go back to checkpoints and see whether a new pack matches that path. After an upgrade, watch for a while: does a new pending
.encappear. Local files are evidence you can look at more than once. You do not need to, and should not, reverse someone else’s client. - Treat every password, token, and internal address that ever entered a real repo as “already left this machine”: rotate it at the original service, and mint a new random password. MyPassGen’s password generator uses a random mode of 6–128 characters, default 16, and warns below 8. It opens without an account. The result is not uploaded as business data. For a reused string, use Strength to check this machine against a common weak-password list. That can prove a hit on a public list. It cannot prove the range of a given snapshot.
- Create a MyPassGen one-time link with the same test sentence, expiry 24 hours, reads left at 1. In chat or a ticket, paste only the part before the hash,
s.html?id=…. Speak the key on a call or in person. Opens without an account. With only the id, the recipient should see an incomplete link; both halves are required to decrypt. After reading, overwrite the clipboard. Do not write an address that still has#into a repo, and do not paste the result page into any assistant chat.
On a company repo, add half a step: ask which roots the assistant opens by default, whether a secrets directory can stay outside the workspace, and whether historical snapshots have an enterprise deletion receipt. MyPassGen will not decide whether a given cloud kept another copy. Trust the windows you just opened and the Git log.
If you have to hand off a key, split it
For a one-to-one handoff the other person can open now, do not write the password into a file that will enter Git, enter an assistant workspace, and linger in the object store. Generate it on this device, then wrap it in a one-time link. On create, the browser encrypts with AES-256-GCM. A single note caps at 32 KB. Reads default to 1 and cap at 10. Expiry can be 1 hour, 24 hours, 7 days, or count-only with no TTL. The server parks ciphertext only. The key sits after # in the URL, so access logs and Referer do not see that slice. A repo and an assistant chat box can still see it. Do not write the full link into a commit.
When a repo or a ticket still needs an entry point, split the channel. The file carries only the owner, the id, and the sentence “key by phone.” A call, an in-person handoff, or a different messenger account carries only the slice after #. Neither half decrypts alone. That is a usage pattern, not a product default. The create page still emits one full link, which is convenient for a one-to-one send. On a channel that draws preview cards, run a test link first and see whether the preview counts a read, as in if you paste a one-time link into Slack or WeChat, does the preview burn it first. Redact error and config excerpts in Clean URL / redaction before you decide whether they still need to enter an assistant.
A key pack or export larger than 32 KB does not belong on a one-time text link. Use the file encryption box: streaming AES-256-GCM in the browser, one file up to 5 GB, output .lock / .enc, passphrase sent separately. Encrypt on this device first, then sync; the other side should see ciphertext only. Pushing an unredacted .env into Git is the same class of problem as pushing an unencrypted certificate pack into a drive: the copy that proves “this is the key” left the working tree you thought you had already cleaned. How to check on the spot that browser encryption did not send plaintext as business data is in how to verify that browser encryption did not upload plaintext.
After you have checked “does checkpoints hold an .enc for this path,” “can git log still print the test password,” and “does upgrading the client alone clear an old snapshot,” you can already answer this article’s question: deleting .env from this tree does not empty the object store, and it does not empty a snapshot already packed. The official fix covers the client path they can change. A pack the server already received, and plaintext in your old commits, do not empty because you upgraded once. The local directory and the Git log are a place to check. They are a poor place to assume “I never pasted the key into the chat box, so this is over.”
FAQ
I already upgraded. Are the old snapshots gone too?
Those are different jobs. A new build blocks the next credential request and the direct upload. An old .enc on this machine, the status file, and the cloud copy the company says is “destroyed after generation” are not inside that upgrade click. Trust whether checkpoints still sit here, and whether a live key has been rotated.
This tree already gitignored .env. Do I still have to rotate?
Read history, not this tree. An ignore rule only blocks future tracking. If an old commit held plaintext, treat it as already copied. Checking once with git log on a test repo is more direct than trusting a filter.
The snapshot is encrypted. Can I treat that as never leaked?
No. The write-up says the private key stays in the cloud. You cannot open the local ciphertext yourself. Encryption stops “a plaintext tar sitting in the bucket.” It does not mean “only the repo owner can read.” If destruction cannot be proved from outside, treat keys that ever entered Git as material you have to rotate.
Do create and read need an account? If I send the wrong channel, can support recover it?
No sign-up. Create and read are public to a visitor. After ciphertext burns by count or expiry, there is no server-side plaintext backup and no support inbox that can recover it. Generate a new password and a new link. Do not keep refreshing the same URL to see if it comes back, and do not write the result page into a repo to resend it.