July 15, 2026
Every Backend Restart Looked Like A Logout
A three-part fix untangled a session bug where a vault re-seal, a lost websocket send, and a plain crash were all indistinguishable from being logged out.
Restart the backend for any reason — a deploy, a crash, whatever — and the frontend used to read that as “you’re logged out” and throw you at the login page. Your session was fine. Your vault just needed unlocking again, in place, like it does after any restart. The interface couldn’t tell the difference, so it picked the worse of the two answers every time.
Root cause: one endpoint conflating two states that have nothing to do with each other. /api/auth/status force-set has_session to false whenever the vault was locked, so “your cookie expired” and “the vault re-sealed on restart” produced the exact same signal, and both the heartbeat and router hard-redirect to /login/ on false. Fix was to stop lying: has_session now reflects the cookie alone, and vault_state carries the seal separately. A restart now surfaces the unlock overlay over your still-mounted conversation instead of booting you out of it.
That only closes the redirect half. The other half was worse because it was silent: two send paths — posting a chat message, firing an action — used raw fetch() calls that never checked for a 401, so a message sent right after session expiry looked like it went through while the spinner just sat there, unresolved until the heartbeat noticed five minutes later. Gave both paths the same 401 detection the API client already had elsewhere. Crash recovery itself was thin too: the launch script only restarted on one specific “I meant to do this” exit code. An actual crash or OOM took the whole process down for good until a person noticed and restarted it by hand. Now it backs off exponentially and gives up loudly after five rapid crashes.
Three different failure modes, same user-facing symptom: it looks like you got logged out. All three are #1878, shipped as three separate PRs today.
Second thread of the day: quieter bugs sitting wrong for a while. The vector-embedding upsert used INSERT OR REPLACE against tables with no conflict resolution — it raised, got swallowed by a warning net, and every re-embed of an existing row silently kept the stale vector. Episodes, lists, scheduled items, all replaced with an actual delete-then-insert. Separately, reasoning models occasionally handed back an unclosed <think> block glued straight onto the reply, and the strip utility only matched closed pairs — so raw chain-of-thought landed as the visible label on a thread. Same shape of bug twice: code covering the common case and skipping the one that actually happens in production.
The read/vision pipeline got rebuilt too — TextReader and ImageDescription are now the one path anything takes to turn a file into text, replacing logic duplicated inside each tool. Deleted normalize_text along the way: it had been collapsing whitespace on every document passing through it, including code and tables, where whitespace means something. Not a formatting nit — documents coming back corrupted from ingestion.
On the feature side: thinking levels now map to whatever a provider actually calls that setting — a thinking block, reasoning_effort, a token budget, a think flag — instead of the app hoping one generic level means the same thing everywhere. And long conversations now evict off-screen turns past a retained window instead of keeping every message mounted forever, so the DOM stops growing without bound as a thread gets long.
All of it lands on the same branch the memory rewrite is riding toward. That branch is getting heavier by the day — next pass is probably deciding what’s actually left before it ships.
-
Fixed a three-part session bug (
#1878): backend restarts falsely triggered a logout redirect instead of an in-place vault unlock, silent 401s on message-send left the UI hanging with no signal, and a real crash had no recovery path at all -
Fixed vector-embedding upserts silently no-op’ing on re-embed across episodes, lists, and scheduled items
-
Fixed unclosed chain-of-thought blocks leaking into stored thread labels instead of being stripped
-
Rebuilt the read/vision pipeline into two owning services and deleted a whitespace-normalizer that was corrupting code and tables on ingestion
-
Mapped thinking levels to each provider’s real native setting, and capped long-conversation DOM growth by evicting off-screen turns