Privacy

What changed on 8 September

A page for the terminal client that shipped without one, settings that no longer scroll sideways, and a prompt builder that finally uses your answers.

zerocode is our terminal client. It had been built and published for weeks and nothing in the app linked to it. There is now a page with the install commands, how pairing works, the command list, and the machines paired to your account. The version comes from the published manifest, so it cannot drift from what you actually download. zerocode sync does not exist yet, and the page says so instead of implying otherwise; it documents the way that works today with rclone.

Mounting a workspace as a network drive took two commands and administrator rights. Most companies do not give staff administrator rights, which made the instructions useless for the people who needed them. It is one command now, and in the ordinary case it needs no administrator. If your account may not start the Windows WebClient service, the command prints the one line to hand to your IT department instead of leaving you with an error number.

Settings had twenty-two tabs in a single horizontal strip. Nine were visible. The other thirteen sat behind a sideways scroll nobody expects to find. There are now six sections, twenty-four cards and a search field. Ten links in our own documentation pointed at settings pages that never opened. Those work now.

The prompt builder asked you questions and then ignored your answers, every time. The model was being called and paid for, and it answered correctly, wrapped in a markdown code fence that our parser rejected. The answer vanished with no error and no log line. Your answer now shows up in the rewritten prompt, and if a reply is ever unusable the page says so.

Smaller: five of seven administrative permissions were switches that did nothing, because the routes behind them only checked whether you were an administrator. Three components sat in the codebase with nothing referencing them, one of them a working domain watchlist that now has a page.

The import had no field for a file

The import card in settings was a text box and a button. A ChatGPT export arrives as a conversations.json of tens to hundreds of megabytes, and nobody pastes that anywhere. The server has been able to do all of it since the feature was written: take the file, show you what it found, then write it. None of the three steps had a control. You can now choose a file or drop one, see which provider was recognised and how many conversations are in it before anything is saved, and discard the wrong one. Archives above ninety megabytes go up in pieces, so the ones that used to die silently at our edge now arrive.

There is a flag on every account that decides whether an administrator may download your conversations. It is off unless you turn it on, every attempt is written down including the refused ones, and until today nobody could see it or change it. It sits in Settings under Data. The list of who has allowed it, and the log of every attempt, is in the admin console. Administrators cannot set it on your behalf, and there is no route that would let them.

Seven places where we charge for a model call recorded neither what it was for nor how long it took. Side calls read as ordinary chat turns in the cost view, and waiting time was missing where it is longest. Those are filled in. We also took back a value we invented this morning in a column that already had a fixed meaning, and corrected the three rows carrying it.

Smaller: the site builder can generate images for the pages it writes. Account names can be changed from the admin console, which says first how many hostnames would keep the old name. And the record of what our sandboxes may reach, eleven thousand decisions, is finally something a person can look at.

When a provider goes quiet

Every request we make to a model provider had one limit: fifteen minutes for the whole thing. Nothing watched the gap between one piece of the answer and the next. If a provider accepted the request, answered with a success code and then simply stopped sending, we sat there until that fifteen minutes ran out. One turn took 942 seconds to produce 679 words worth of output. Another produced nothing at all for almost four minutes before the person watching it gave up.

There are now two limits instead of one, because there are two different kinds of waiting. Before the first piece of an answer arrives, a model that reasons before it speaks is allowed three minutes: cutting that short would break the models you would pick for hard questions. After the answer has started, a gap of ninety seconds means the provider has stopped, and we stop with it. Both numbers are settings, not something baked into the code.

What you already received is kept. The partial answer stays in the conversation, marked as interrupted, and what it cost is recorded rather than quietly written off. The difference is that you find out in ninety seconds instead of fifteen minutes.

The number in our own admin view was also wrong, in a smaller way. It said "average latency" over a figure that measures a whole turn, tool runs included. A turn with 28 rounds of tool use is not a slow model. It now says what it measures, in seconds, with the slowest turn beside the average.

A failed run can be tried again

Long jobs check in as they go, so an interrupted one carries on where it stopped rather than starting over. That already happened whenever the worker restarted. It did not happen when a run ended in an error: those were final. A site build that ran for sixteen minutes and stopped at ninety-six per cent, on a cause that has since been fixed, could not be picked up again. The work and the money were simply gone.

Failed runs now have a Try again button, and it starts at the last checkpoint rather than at the beginning. Nothing retries on its own: if the cause has not changed, an automatic retry would burn through the same money to reach the same wall. The decision is yours, which means you need the reason in front of you.

You did not have it. The job list carried the state and the point where things stopped, but not the error text, even though it had been sitting in the database the whole time. "Failed" at "96% report" is not something anyone can act on. The reason now appears on the run itself, in full, the way the tool wrote it. When you try again, it is cleared, because an old error next to a running job reads as a statement about now.

Paused runs are unchanged: those stopped at a limit, and continuing one still asks you to raise it first. A failure is a different thing, and asking for a fresh allowance would be a question with no answer.

A dot for when we are deploying

Shipping an update restarts the server. It takes a few seconds, and in those seconds any request in flight simply fails. What you saw was "Network error", which is true in the narrowest sense and useless in every other: it looks like your connection, or ours breaking, and the sensible response is to worry or to reload and lose your place.

Our deploy script has marked the moment for as long as it has existed. It sets a flag before the restart and clears it after the last check. The step is even called the maintenance banner. There was no banner: nothing read the flag, on the server or anywhere else.

Now a small dot appears at the top right while a deploy is running, with the word and how long it has been going. It is there for everyone, not only for administrators, because the restart interrupts whoever is typing. It disappears when the deploy finishes. A dot that is always present and only changes colour becomes wallpaper, so this one shows up when it has something to say and stays quiet otherwise.

The part that matters more is the message. A request that fails during a deploy now says the server is restarting rather than reporting a network error. That claim is deliberately narrow: it appears only when a deploy was actually reported within the last three minutes. A real outage dressed up as routine maintenance would be the more expensive lie, and we would rather show you the blunt error than the reassuring one.

One more state, for honesty: if a deploy gets stuck between setting the flag and clearing it, the dot says the flag has been left behind rather than claiming work is still going on.

Idle workspaces now move to storage

Your container keeps a workspace on the server disk. Until today it stayed there whether or not anyone used it, because the part that moves it to storage existed and nothing ever called it. A sweeper now runs every five minutes and moves anything idle for thirty minutes. You can change the interval, and setting it to zero turns the whole thing off.

It is a move now, not a copy

The old code wrote an archive to storage and left the workspace on the disk, so nothing was ever freed. It could not delete it: the files belong to the account inside the container, and the platform runs as a different user. The same limit meant a restore failed with "File exists", which is why this path had never actually run on a real workspace. Both directions now go through a small privileged helper that takes two numbers and no paths. Before the local copy is deleted, the fresh archive is read once end to end. If that read fails, the archive is discarded and nothing is removed.

What the wait actually costs

Starting a container whose workspace is already local takes about a third of a second. Bringing 1.73 GB back from storage took 6.6 seconds in our measurement. Those are different enough that the button should not pretend otherwise, so the container now shows the restore as its own step, with a percentage taken from the bytes actually written rather than from a timer. When we do not know the size, you get a plain spinner instead of an invented number.

Not covered yet: containers on the two worker machines stay where they are. Their disks are not reachable from the machine that does the packing.

Storage now reaches the worker machines

Earlier today we shipped the idle move: a workspace nobody has touched for thirty minutes is packed up and parked on network storage. It only worked for containers on the main machine. Workspaces live on the local disk of whichever machine runs the container, so packing one from somewhere else finds nothing. The two worker machines were skipped.

Packing where the files are

The packing now runs on the machine that owns the workspace, and the two workers got a second network card so they can reach the storage themselves. One of them stayed silent afterwards: address lookups worked and no other packet got through. An isolation rule on that hypervisor lists which guest ports may pass, and it only knew the first network card of each guest. The rule that shields our browser build machine is untouched; only the list of ports was completed.

Files with no container

Deleting a container was supposed to delete its workspace. It never did: the platform runs as one account and the files inside a container belong to another, so the delete failed quietly and the container was reported gone anyway. We checked 46 workspace directories and found 32 that belong to no container, 28 of them on one worker, roughly a gigabyte. They are gone. A sweep now repeats that check every six hours.

It refuses to touch anything parked on storage, anything created in the last hour, and anything whose container is still running, even when the record says it was deleted. If it cannot get a reliable list of what is running on a machine, it removes nothing there.

The storage device now accepts both workers, so this works end to end. A 461 MB workspace there packs to 100 MB and comes back in under three seconds, with every file owner and permission intact.

An optional address, and three claims we withdrew

There was no email field on an account, and so no password reset. That was deliberate, and it is a real privacy property: nothing to leak, nothing to phish.

It cost three things. Lose your password and your recovery codes and you were locked out for good. We had no way to reach you if something happened that you needed to know about, which Swiss and EU law requires us to be able to do. And we could not warn you about a sign-in that was not you.

So an address is optional now. The field starts empty, you can remove it again, and an account without one works exactly as it did before.

A reset gives you the account back, not the content. Your encrypted memory hangs on a separate passphrase that never reaches our servers. Lose that and the memories stay unreadable, to you and to us. We say so in the settings panel, on the reset page, and in the reset email itself.

Sign-ins are recorded now

Time, outcome and browser, readable in settings, kept 90 days. Your address is never stored. We keep a one-way value we can compare against earlier sign-ins, which is enough to tell a familiar one from a strange one and not enough to work out where you were.

Three claims we withdrew

"There is no email column on the user record." There is one now, empty until you fill it. "No email on the account. No password reset either." Now optional, and without one there is still no reset.

"No analytics, no third party scripts, no email on file." The middle part was wrong before today. Cloudflare injects a small script that scrambles our contact address against harvesters. It tracks nobody, but we do not serve it, so saying we had none was inaccurate. Full text in the privacy policy.

The index you could not see

When a conversation gets long, we keep an index of it: older messages, cut into pieces, so a later chat can pull back the part that matters instead of carrying everything. On this installation that index holds 4260 pieces from 34 conversations, and it is queried on every turn. None of it was visible. You could not see what was in it, you could not run the search yourself, and you could not delete it.

The server has had all four of those since the feature was written. The comment above the search route says the point out loud: the person should be able to run the same retrieval the chat runs and see the hits. Nothing called it. Settings now has a Context index card next to Memory: what is indexed and from which source, the same search the chat performs with the same excerpts the model would receive, a source you can switch off, and a delete that actually removes the pieces rather than only stopping new ones. Switching a source off already deleted what it had indexed; now you can tell that it did.

The card also says plainly what kind of search it is. Without an embedding model configured it is keyword matching, and calling that semantic would be a claim we cannot back. No administrator can read this index, and there is no route that would let one.

Separately: a site build could fail at 96 per cent, after sixteen minutes of work, because one tool wrote a line that another tool forbids. Neither belonged to the site being built. Fixed for new sites and for the ones already started.

Corrections are entries here too. If something on this page is wrong, say so. Feed · Docs · Legal & privacy · Home