mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
fix(desktop): retry OAuth cookie read on cold-start jar race (#67769)
A `persist:` partition's cookie store hydrates lazily, so the first cookies.get() on a fresh launch can return empty for a signed-in user. That false-negative made hasLiveOauthSession() throw "not signed in", which on the no-retry initial boot path surfaced as the transient "Hermes couldn't start" OAuth overlay that always cleared on Retry. hasLiveOauthSession now reads once (no added latency on the happy path); only on an empty read does it warm the store (flushStorageData + a throwaway get, memoized) and re-read with a bounded ~180ms backoff before trusting the negative. Genuinely signed-out users still resolve false quickly and get the overlay. Fixes the whole class: the same function backs the reconnect path and the Settings connected indicator.
This commit is contained in:
parent
3aeded6e32
commit
f0aae14c68
1 changed files with 77 additions and 7 deletions
|
|
@ -5212,6 +5212,50 @@ function getOauthSession() {
|
|||
return oauthSession
|
||||
}
|
||||
|
||||
// Cold-start cookie-jar warm-up. A `persist:` partition materialized via
|
||||
// session.fromPartition() loads its on-disk cookie store LAZILY: the very first
|
||||
// cookies.get() on a fresh cold start can resolve BEFORE the jar has finished
|
||||
// hydrating from disk and return an empty array — even though the user is
|
||||
// signed in. That false-negative used to make hasLiveOauthSession() report
|
||||
// "not signed in", which on the initial boot path (startHermes → the renderer's
|
||||
// single-shot boot() with no retry) surfaced as the "Hermes couldn't start"
|
||||
// OAuth overlay that vanishes the instant the user clicks Retry.
|
||||
//
|
||||
// We force the store to hydrate once, up front: flushStorageData() then a
|
||||
// throwaway cookies.get(). The promise is memoized so every caller awaits the
|
||||
// same single warm-up. Best-effort — any error resolves so we fall back to the
|
||||
// live read (which then does its own bounded re-check).
|
||||
let oauthCookieWarmup: Promise<void> | null = null
|
||||
|
||||
function warmOauthCookieStore() {
|
||||
if (oauthCookieWarmup) {
|
||||
return oauthCookieWarmup
|
||||
}
|
||||
|
||||
oauthCookieWarmup = (async () => {
|
||||
const sess = getOauthSession()
|
||||
|
||||
if (!sess) {
|
||||
// App not ready yet — don't memoize a no-op; let a later call retry.
|
||||
oauthCookieWarmup = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// flushStorageData() forces Chromium to reconcile the in-memory cookie
|
||||
// monster with the on-disk SQLite store; the subsequent get() then reads
|
||||
// a populated jar rather than racing the lazy first-access load.
|
||||
sess.flushStorageData?.()
|
||||
await sess.cookies.get({})
|
||||
} catch {
|
||||
// Best effort; the real read below re-checks with bounded retries.
|
||||
}
|
||||
})()
|
||||
|
||||
return oauthCookieWarmup
|
||||
}
|
||||
|
||||
// Bare + prefixed variants of the session cookies live in
|
||||
// connection-config.ts (cookiesHaveSession / cookiesHaveLiveSession). See
|
||||
// that module for details.
|
||||
|
|
@ -5258,19 +5302,45 @@ async function hasLiveOauthSession(baseUrl) {
|
|||
|
||||
const parsed = new URL(baseUrl)
|
||||
|
||||
try {
|
||||
const cookies = await sess.cookies.get({ url: baseUrl })
|
||||
|
||||
return cookiesHaveLiveSession(cookies)
|
||||
} catch {
|
||||
const readLive = async () => {
|
||||
try {
|
||||
const cookies = await sess.cookies.get({ domain: parsed.hostname })
|
||||
const cookies = await sess.cookies.get({ url: baseUrl })
|
||||
|
||||
return cookiesHaveLiveSession(cookies)
|
||||
} catch {
|
||||
return false
|
||||
try {
|
||||
const cookies = await sess.cookies.get({ domain: parsed.hostname })
|
||||
|
||||
return cookiesHaveLiveSession(cookies)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First read against the (possibly still-hydrating) jar.
|
||||
if (await readLive()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Cold-start false-negative guard. A `persist:` partition's cookie store
|
||||
// loads lazily, so the FIRST read on a fresh boot can come back empty even
|
||||
// for a signed-in user — the exact race that produced the transient "Hermes
|
||||
// couldn't start / not signed in" overlay that Retry always cleared. Before
|
||||
// trusting a negative, force the store to hydrate and re-read a couple of
|
||||
// times with a short backoff. A genuinely signed-out user still resolves
|
||||
// false quickly (≤ ~180ms); a signed-in user racing the load now wins.
|
||||
await warmOauthCookieStore()
|
||||
|
||||
for (const delayMs of [30, 60, 90]) {
|
||||
if (await readLive()) {
|
||||
return true
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs))
|
||||
}
|
||||
|
||||
return readLive()
|
||||
}
|
||||
|
||||
async function clearOauthSession(baseUrl) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue