Repository navigation
Description
Four findings from taking over #764, split out to keep that PR reviewable. All
four were verified against the code; where a claim is inspection-only it says so.
Ranked by how much each one unblocks.
Context that makes item 1 matter more than it looks: the chooser state cannot be
staged locally. Two cookie-surgery hypotheses were tried and refuted — deleting
only the Flow session cookie yields FlowSessionOutcome.GOOGLE_SESSION_ONLY and no
chooser; keeping only ACCOUNT_CHOOSER/__Host-GAPS and dropping every Google auth
cookie also yields no chooser. Both were predictable in hindsight: when the chooser
did appear live on 2026-09-09, the log showed flow_session_cookie_present=True,
expired=False, SAPISID present. Google decides this server-side. So the only route
to evidence is capturing it when it next happens on its own.
1. FlowAccountChooserError is declared capture-worthy and raised where capture cannot run (verified)
FlowAccountChooserError is in both _capture_triggers() and _screenshot_triggers()
(diagnostics.py:1850-1892) and incident_capture defaults True. But capture only
fires through _raise_with_incident, whose docstring calls it "the shared failure
boundary for client entry points" — and every call site is one (image_generation,
image_batch, image_upscale, video_generation, character_generation). The chooser
raises inside _bootstrap_and_resolve_locale (client.py:886), which is not an entry
point. Confirmed empirically: the live 2026-09-09 failure left no bundle in incidents/.
Why this is top of the list. No bundle and no screenshot for the one failure whose
DOM would settle whether [data-email] is Google's live anchor. That question is what
this whole feature rests on, and right now the only evidence for it is a single
accidental live run. A screenshot on the next occurrence answers it permanently.
It also gates items 3 and 4 below, which are currently unfalsifiable for the same reason.
2. A plain sign-in page is reported as an account chooser (verified)
The gate accepts any https accounts.google.com URL except the rejected-browser route
(client.py:806-821). Land on /v3/signin/identifier — no remembered accounts, just a
sign-in form — and the row count is 0, producing:
Account chooser displayed at ... but recorded account 'x' was not found among
selectable accounts.
That asserts a chooser listing other accounts when there is no chooser at all, and it
points at the wrong remediation: the operator needs to sign in, not pick differently.
#763 already names two distinct post-hop landings (/about and the chooser); this is a
third. Cheap to distinguish — a chooser has [data-email] rows, a sign-in page has none
— and testable offline today against the existing route-interception e2e harness.
3. --account mismatch leaves the wrong-account profile in place (verified)
cli.py:296-310 reports the mismatch (error text plus one auth.account_assert_failed
log line) and raises exit 38. Nothing quarantines, renames or records it. A later run
re-reads a profile authenticated as someone else with no persisted signal — and on a
billed product that is the wrong account paying. This was a deliberate "minimum" in
review round 4 of #764; recording it so the decision is revisitable rather than lost.
4. Single-pass assumption (inspection only, not reproduced)
_handle_account_chooser runs once per bootstrap. Google can chain hops — chooser →
consent → chooser. A second chooser after the first click is not handled and degrades
into the landing timeout. Unfalsifiable until item 1 lands.
5. The landing timeout is still an unmeasured number
timeout=30_000 at client.py:862 entered in 41c4ae20 as a bare literal with no
rationale, and it is exactly Playwright's default — which this repo already documents as
the cause of a 34s silent hang (incident #56), against a house style where
URL_SETTLE_TIMEOUT_MS ("8s made ffroliva setup take 11.2s") and
_LANG_SETTLE_TIMEOUT_MS (measured 2.26/2.49s) both cite measurements. Deliberately left
alone in #764: there is no evidence it is too short or too long, and guessing is what put
it there. The landing URL added in 56c71017 is what will produce that evidence.
Activity
ffroliva commentedon Sep 9, 2026
Correction to item 1 — do NOT wire capture up as written
Item 1 above recommends routing
FlowAccountChooserErrorthrough incident capture toget a screenshot of the real chooser. That recommendation is wrong and should not be
acted on. An
xhighcode review caught it and I verified the reasoning.FlowAccountChooserErrorfires, by construction, only whenpage.urlis onaccounts.google.com. Capturing there writes a DOM dump and a full-page screenshot of aGoogle auth surface into the bundle — every signed-in identity's address, display name
and avatar, and on a sign-in form the input and hidden-field DOM. That bundle is the
artifact users are prompted to attach to GitHub issues.
It also contradicts the codebase's own stated policy.
diagnostics.py:1030excludesAuthExpiredErrorfrom capture as "deterministic operator remediation; DOM adds nothing",and
docs/DEBUGGING.mdsays "Never captured: ... ordinary AuthExpiredError".FlowAccountChooserErrorhas the same remediation and the opposite treatment — it iscurrently in both
_capture_triggers()and_screenshot_triggers().So the finding inverts: the defect is not that capture is unreachable, it is that the
class is registered for capture at all. The unreachability is what has been
protecting users from it. The fix is to remove it from both trigger lists, matching its
policy twin.
That leaves the open question — is
[data-email]still Google's live anchor? — withoutthe route I proposed. A redacted, structural answer would be needed instead: record which
selector tier matched (attribute vs text fallback) and the row count, not the DOM. That
is a separate design question and I am not folding it in here.
Item 4 (single-pass assumption) said it was gated on item 1; it is not — it is gated on a
live occurrence, same as item 5.
fix(auth): autoselect the recorded account at the post-migration choo…
ffroliva commentedon Sep 10, 2026
Verified on
develop@132dc977(the #764 merge): items 1 and 2 are already fixed, both by #764 itself. Recording it here so neither gets re-implemented.Item 1 — capture is unreachable → resolved as you redirected it
Your correction was to remove
FlowAccountChooserErrorfrom the trigger lists rather than route capture to it. That is what shipped:It is now in the
ContentPolicyError, AuthExpiredErrorexclusion atdiagnostics.py:1030, carrying the reasoning verbatim — "a bundle would carry a DOM dump and a full-page screenshot of a Google auth surface into the artifact users are prompted to attach to GitHub issues".git log -Sputs that line in132dc977.So the
[data-email]question is still open, but the privacy defect is closed and the class now matches its policy twin.Item 2 — a sign-in page reported as a chooser → fixed
client.py:830-836no longer accepts everyaccounts.google.comlanding. Two independent positive signals, either sufficient:Covered by
tests/e2e/test_account_chooser_e2e.py::test_e2e_signin_page_is_not_reported_as_a_chooser.Still open
--accountmismatch leaves the profile in place) — the deliberate round-4 minimum, unchanged.timeout=30_000) — untouched.Related, and now fixed
Item 2 is one of three sites where a known landing state was reported as the wrong thing. The other two —
flow.google.com/aboutreported asUiSelectorDriftError(#756) andlabs.google/fx/api/auth/signin?error=Callbackreported asCould not find 'New project' CTA(the 2026-09-10 RED canary, #559) — are fixed together inbugfix/landing-state-not-drift, with a sharedflow_landing_kind()next toflow_host_kind(). Item 2 needed no change there because #764 had already handled theaccounts.google.comcase.fix(transports): route Flow's unavailable screen to its own error (ex…
fix(transports): close the labs arm of the unavailable-screen gap, an…
fix(errors,migrated): stop reporting two things we never observed (#803…