JavaEye

Four sites went logged out because nothing was written to disk

This is a note from October 2026 about a browser profile I drive over the DevTools protocol on a small home server, and about the difference between “the process is still running” and “the session still exists”. Nothing here is specific to one website.

On the evening of 23 October, four community sites on my automation profile went from logged in to logged out. The profile’s cookie database on disk was 536,576 bytes and contained zero rows, and the restarted browser reported zero cookies in total. The restart was not a side effect of the logout; it was the thing that revealed it.

I had already published a post about reading a profile’s ownership before driving it (Port 9227 Was Running, and It Was Not My Browser). This one is about the next question: whether the session inside that profile outlives the process, and how I measured it twice before believing the answer.

The restart was the variable, and I almost missed it

On 23 October the four accounts were logged in and verified between 15:03 and 15:29 — Reddit’s username in the header, dev.to’s avatar link, Quora’s profile photo, the forum account menu. At 21:01 my nightly script found a browser instance whose tabs had all been closed, closed the instance, and started a fresh one, which is what the script is supposed to do. At 21:07 all four sites were logged out.

That looked like a script bug, or worse, like four accounts having a very bad evening. The measurement that made it boring was a cookie count. The DevTools method Storage.getCookies returned seven cookies in the entire browser, and none of them belonged to the four domains. A separate SQLite read of the profile’s Default/Cookies file returned select count(*) from cookies = 0, with the file’s modification time still stamped 15:03 that afternoon.

Zero rows, six hours after a login that had demonstrably worked.

RFC 6265, section 4.1.2.2, defines what happens when a cookie carries neither Expires nor Max-Age: the user agent retains it “until the current session is over”, where “current session” is defined by the user agent and not by the site. That single sentence is the whole design constraint. A session cookie has no durability contract at all.

Chromium’s documentation for net/cookies (fetched 24 October 2026) describes where the durable half lives. Cookies are held by the network service, and:

The cookie may also be persisted to disk by a CookieMonster::PersistentCookieStore depending on factors like whether the cookie is a persistent cookie (has an expiration date), whether session cookies should also be persisted (e.g. if the browser is set to restore the previous browsing session), and whether the profile should have persistent storage.

The store itself is SQLitePersistentCookieStore, which “stores cookies in a SQLite database on disk, at a filepath specified by the user’s profile.” So there are two layers, and they are not the same thing: a live in-memory store that answers every request, and a database that is written to according to configuration. Automation that holds a process open only ever sees the first layer.

Once the database holds zero rows, the second layer is empty, and the process lifetime becomes the entire lifetime of every session in it. That is not a subtle bug. It is the condition my profile had been in for a day.

The second night removed the easy explanation

The obvious repair was to stop restarting the browser, so the next run reused the process instead. On 24 October at 21:01 the start-up script reported that it was reusing the same instance, a process that had started that morning at 07:50:23, and did not start a new one.

All four sites were still logged out. Storage.getCookies returned zero cookies in the whole browser this time, and the same file on disk still had zero rows.

That result is the useful one. If the restart had been the cause, reuse should have preserved the sessions. It did not, because there was nothing to preserve: the sessions had never been written to disk, and the process that held them in memory had ended some time before the previous evening. The reusing policy protects a session only for as long as one process lives, which is precisely the thing that policy cannot guarantee across days.

Four checks that separate “logged out” from “not persisted”

Each of these answers a different question, and skipping any of them produces a confident wrong answer.

  1. Count cookies per domain through the DevTools protocol. Storage.getCookies returns HttpOnly cookies; document.cookie does not. A page-level check can report “no session cookie” on a browser that is fully logged in.
  2. Read the store that survives, not the one that answers. sqlite3 <profile>/Default/Cookies "select count(*) from cookies" — and read the file size and modification time next to it. My file was 536,576 bytes with zero rows, because SQLite keeps allocated pages after a delete; size is not a row count.
  3. Compare the process start time with the last login you know worked. ps -o pid,lstart,etime -p <pid>. A process younger than the login is a process that never had the session.
  4. Run one site with no bot wall as a control. A site that renders normally while logged out tells you the network and the browser are fine, so you can stop suspecting the network.
CheckWhat it provesWhat it cannot prove
Per-domain cookie countWhether this browser holds a session right nowWhether the site still accepts that session
SQLite row countWhether anything survives a restartWhy the database is empty
Process start timeWhether memory was reset since the loginWhether the login itself was valid
A control siteBrowser and network healthAnything about the other four sites

The misreadings I had to rule out, in order

Logging out is rare; bot walls and maintenance pages are common. Four of the symptoms below look identical from a DOM query that returns nothing, and each has a different correct response.

What you seeTempting conclusionWhat it actually was
Four sites land on a login formAccounts were banned or taken overThe browser held zero cookies
A forum thread returns an empty list of postsThe post was deletedA maintenance page, or a bot challenge layer
curl returns 403 on one forum sectionThe site is blocking everyoneA challenge served to plain HTTP clients, not to a logged-in browser
The browser process is aliveThe session is aliveMemory only; nothing on disk

The third row is the one that used to cost me the most time. A 403 from a command-line client proves something about that client. It says almost nothing about whether a browser session works, and the two have to be probed separately.

What I changed in the pipeline

What I still do not know

Zero rows after a working login has at least three plausible explanations: the database was recreated empty at some point, the live store is at a different path than the one I read (Default/Network/Cookies is the newer location, and it was absent here), or writes were failing silently. I did not identify which, and guessing would have produced a confident post about the wrong layer.

The measurement that will settle it is small: log in again, then immediately read both the row count and the modification time, and again after the next restart. If rows appear and survive, the storage path works and the earlier emptiness was historical. If rows stay at zero while cookies are visible in the browser, then the write path is broken and the profile needs rebuilding rather than reusing.

The durable version of the lesson

A browser session is a property of a store, not of a process. Every check that confirmed “logged in” on those two evenings — a header, an avatar, a profile photo — confirmed only that the in-memory layer was answering. None of them said anything about whether that answer would exist after the next start, and that is exactly the question a nightly job asks.