Don't crash the prepare flow when the browser closes during Google login detection#64
Conversation
The login detector for `auth browser-prepare google-*` listens for console.cloud.google.com responses and asynchronously reads each response body to decide whether the user is past the sign-in page. That `response.text()` promise has no `.catch()` handler, so when the page or context closes while a body is still being read (e.g. the automation navigates onward, or the user closes the browser window), Node sees an unhandled rejection and terminates the entire prepare process — at which point any GCP project / OAuth client already created by the flow is orphaned in the user's account because latchkey never reaches the point where it persists the credentials. Add a `.catch()` that swallows the closed-page rejection. The login detector treats a response we couldn't read as inconclusive (same as not seeing it at all), so this is behaviour-preserving for the happy path and only changes the failure mode for the closed-page race from "crash the process" to "keep polling until login is detected or the flow times out". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
d02bba2 to
24e3c5c
Compare
|
Reproduced this on macOS today — same uncaught rejection signature, same stack: Environment: macOS Darwin 25.4.0, Node v22.22.0, latchkey 2.10.1 (driven from minds.app's permission grant flow, which runs This matches your WSL2 trace exactly, so it doesn't look macOS-specific — just timing-dependent. A second prepare attempt against a fresh state hit a different failure mode ( The narrow-scope catch on |
|
@boweiliu @weishi-imbue Thank you! |
Why the change was needed
auth browser-prepare google-*listens forconsole.cloud.google.comresponses to detect when the user is past the Google sign-in page. The handler asynchronously reads each response body viaresponse.text(), but does not attach a.catch(). When the page or context closes while a body read is still in flight — either because the automation navigates onward (which the flow does many times during project creation, API enabling, and OAuth-client creation) or because the user closes the browser window — Node sees an unhandled rejection and terminates the entire prepare process.In practice this kills
prepare()mid-flight, which means any GCP project and OAuth client it has already created in the user's Google account get orphaned (latchkey only persists the credentials afterprepare()returns successfully).How this change fixes it
Attach a narrowly-scoped
.catch()to the fire-and-forgetresponse.text()chain that swallows only closed-page rejections — i.e. the ones already classified by the existingisBrowserClosedErrorhelper (matchestarget closed,browser/context/page has been closed,net::ERR_ABORTED). Any other rejection is rethrown, so genuinely unexpected errors still surface. The login detector already treats a response it can't classify as inconclusive (same as not seeing it at all), so this is behaviour-preserving on the happy path. The only thing that changes is the failure mode for the closed-page race: instead of crashing the process, the polling loop inwaitForGoogleLoginkeeps running until login is detected or the existing timeout fires.Caveats
The crash was observed on WSL2 (Ubuntu 22.04, Node 24, Playwright Chromium 145). It has not been reproduced on macOS, so the timing of the race may differ there.
🤖 Generated with Claude Code