Skip to content
Ward

A cookie count taken the instant after close is not a count, it is a zero

Chrome's file handles outlive its process exit on Windows. A profile holding 431 cookies journalled as having lost every one of them.

5 min readDivyansh Singh

On 8 August a warm-up sitting finished and the journal recorded that the profile had lost 431 cookies. Every cookie it had. The profile in question had been running normally, had visited real sites, and should have gained a few rather than losing all of them.

A minute later the same profile read 450 cookies.

Nothing had been lost. The measurement was wrong, and it was wrong for a reason that is specific to Windows and general to anything that reads a file a program has just stopped using.

What actually happened

Chrome's file handles outlive its process exit on Windows. The process is gone from the task list, the exit code has been collected, and the handles on the profile's SQLite files are still open for a short period afterwards.

The warm-up runner took its "after" cookie count immediately following the browser close, which is exactly the moment those handles are still held. The reader could not open the locked database. It returned zero.

Zero then went into the arithmetic. Before: 431. After: 0. Difference: minus 431. The sitting was journalled as having destroyed the profile it was supposed to be warming up.

Why the reader returned zero

Because the reader was written to be quiet. Counting cookies is a nice-to-have on a journal entry; a warm-up sitting should not fail because a count could not be taken. So the failure path returned zero rather than raising.

That is the correct instinct and the wrong implementation, and the distinction is the whole point of this post.

Quiet is right. A diagnostic figure must not take down the operation it is describing.

Zero is wrong. Zero is a valid count. It is what an empty profile has. Returning it for "I could not look" collapses two completely different states into one number, and every consumer downstream treats the number as a fact.

The same shape has appeared twice more in this codebase. A cookie database at a path that moved reads as an empty profile. An installer that declined to install anything returns success. In all three, a failure was encoded as a plausible value instead of as an absence, and in all three the consequence was a confident, specific, wrong statement to the operator.

What a quiet failure should return

Something the caller cannot mistake for data.

ApproachWhat the caller seesWhat goes wrong
Return 0A numberReported as data loss
Return -1A numberReported as data loss, in a stranger unit
Return an absent valueNothing to arithmetic onCaller must decide, which is the point
RaiseAn errorTakes down an operation that should survive

Returning an absent value is the design. A count that could not be taken should be absent, the journal entry should say the count is unknown, and nobody should be able to subtract it.

The same Windows behaviour breaks test cleanup, and it breaks it after the assertions have already passed.

A temporary directory holding a profile fails to delete because Chrome still has handles open on it, and the framework reports that as a build failure attached to a test that succeeded. The first time you see it, you will look for a bug in the test. There is not one; there is a browser that has not finished letting go.

What to do instead

The instinct is to move the measurement - read the count while the browser is running instead. That is worse, and it is worth saying why, because it is the first fix everybody reaches for.

A cookie database read mid-sitting is being written to while you read it. The number you get is a number the profile held for an instant, and the difference between two such numbers is not the gain, it is the gain plus whatever the browser did between the two reads. A count taken during the sitting belongs in a log as a warning, not in the arithmetic. The two moments the file is neither held by a live browser nor half-written are before the browser opens and after it has closed, and those are the two ends that are still used.

So the fix was not to move the reading. It was to let it fail.

The count now retries a handful of times a few hundred milliseconds apart - a locked database is a normal thing to observe for a second, and it is not worth failing a sitting over - and when it still cannot be taken, it returns an absent value rather than a number. Both ends go through that same path. The journal records the count as unknown and nothing subtracts it.

That second detail took a second pass to get right, and the reason is the interesting part. The "after" count was fixed first, and the "before" count was left answering zero for a while afterwards. It is the same fact with the sign flipped: a profile whose previous window the operator closed a few seconds ago also reads as locked, so a failed "before" of zero on a profile holding four hundred cookies reports a gain of four hundred that never happened. A fabricated gain is quieter than a fabricated loss and it is the same defect.

And the general rule, which is the third time it has come up in this tranche and will not be the last: when something cannot be measured, say so. Do not return a number that looks like a measurement. Somebody downstream will believe it, and the person they tell will be the operator, and what the operator will hear is that their data is gone.

How this post was checked

Data source
A real warm-up sitting on 8 August 2026 against a Windows 11 profile holding 431 cookies, counted immediately after the browser closed and again a minute later once Google Chrome 151 had gone
Measured with
Ward's warm-up journal and its cookie jar reader, reading the profile's own SQLite cookie database
The claim
The count taken at close is a measurement of a file lock rather than of the profile, and it reports a full profile as having lost everything.

Divyansh Singh

Builds Ward at Digital Heroes

Divyansh Singh builds Ward, a Windows manager for many isolated browser profiles, at Digital Heroes. Most of his week is spent in the Chromium command line, the DevTools Protocol and Windows process behaviour. Every measurement quoted in these posts was taken on the machine Ward is built and tested on, with the browser or tool version written down beside the result, and the ones that contradicted what the documentation said are the ones that became posts.

Last reviewed . Corrections and bug reports: support@digitalheroes.co.in.

Ward is the desktop application these measurements came out of

Many browser profiles on one PC, each with its own proxy and its own device identity. Free plan, free account, nothing to buy today.

Related