Repository navigation
feat(auth): re-mint an expired Flow session without a click - #2
Merged
Merged
Conversation
The labs.google NextAuth cookie expires on its own schedule, independently of the Google SSO session. When it did, REST calls failed with `AisandboxAuthError: no access_token in /fx/api/auth/session` (exit 3) and the user had to click Flow's "Sign in" by hand - a click that needs no input, because Google still holds the SSO session. FlowApiClient._fetch_access_token now performs that round trip itself on the first missing (or non-JSON) session: NextAuth's csrf + signin/google API returns the Google OAuth URL, a temporary page follows it, and Google redirects straight back with a fresh session. Selector-free, so locale-invariant. One attempt per client, serialized across pooled pages; a password / 2FA / CAPTCHA / account-chooser page stops it at once and the existing exit 3 stands. Log event: auth.flow_session_refresh. Live-verified (zero credits) on a copy of a real profile by tests/e2e/test_auto_relogin_e2e.py: clear the session cookie in a live context, get a fresh access token back, outcome=ok.
Conflicts: CHANGELOG.md (both entries kept), client.py imports (both kept). The base added session-expiry reporting (#e72c2d15, #ce547f42) that measured a lapsed session and concluded it "cannot be revived". That holds for re-reading the session endpoint, not for a fresh NextAuth sign-in. Measured 2026-09-18 on a copy of a genuinely lapsed profile (past `expires`, `error: ACCESS_TOKEN_REFRESH_NEEDED`): the round trip returned a future `expires` and no `error`, with no click. The merge therefore adapts: - The refresh fires on a stale session, not only a missing token: a lapsed session still carries its old `access_token`, so absence never triggered. - A successful refresh writes the new deadline to `.gflow_session_expires`. - At client start, when that cache says the session lapsed, it is revived before the run (cookie-authenticated tRPC calls would otherwise just 401). Failure is logged, never raised. - verification: `_is_stale` / `_parse_expires` made public for the client. - The pre-flight hint, KNOWN_ISSUES, AUTHENTICATION.md and the base's CHANGELOG entry no longer claim a lapsed session cannot be revived. Live: lapsed copy of kittinansr2 through FlowApiClient start -> client.flow_session_expired, auth.flow_session_refresh outcome=ok, cache moved to the next day.
kittinan
pushed a commit
that referenced
this pull request
Oct 3, 2026
…oliva#895) (ffroliva#899) * fix(video): retry the signed-media download and type its failure (ffroliva#895) A finished, billed Veo clip was discarded when its transfer hit a transient connection reset. The GET was issued once, with no retry, and the resulting `playwright.async_api.Error` escaped the typed-error contract entirely, surfacing as `Unexpected error ... exit code 1, retryable: False` -- which told the user nothing about a clip they had already paid for. The transfer now survives a dropped connection using Playwright's own `max_retries`, not a new retry policy: it matches on the driver's `e.code === "ECONNRESET"`, never on an HTTP status, and its backoff is charged to the same timeout rather than multiplying it. That last property is what makes it safe here -- our download holds `_generate_lock`, shared with every other generation on the client, so a per-attempt timeout would have turned one 180 s wait into nine minutes. Both claims were measured rather than read from the driver source, A/B-controlled against a local socket emitting a real TCP RST (docs/superpowers/spikes/ 2026-09-22-playwright-max-retries-econnreset.md): the retry recovers, a control with `max_retries=0` fails on the identical fault, and three attempts at a 5 s timeout finish in 1.57 s rather than ~15 s. Fixed at all three download sites, not just the one the traceback named. The second is `gflow data download`'s own GET -- the escape hatch could be stranded by the fault it exists to rescue. An exhausted transfer now raises `NetworkError` (exit 6, correctly retryable) naming the media id and the recovery command. Exit 6 is not new: USAGE.md already documented it as "Network failure persisted across 3 attempts". The Playwright message is deliberately not forwarded into `detail` -- it embeds the driver's server-side call log including the request URL, and `detail` reaches stderr and --json stdout without passing through redaction. Also fixed on the same paths: every 4xx branch fell through to `WireFormatError`'s class default, "retry with a simpler prompt text", on routes carrying no prompt and no payload -- the same wrong-advice class removed in ffroliva#875. And the labs download, whose route is a 302 by design and so cannot use `max_redirects=0`, now verifies where the chain landed before writing the bytes. Both MCP twins flattened this worse than the CLI and are fixed by the typing: `gflow_download_media` returned "Unexpected Error; details were logged server-side" with no remediation key at all, and the queued `gflow_generate_video` path returned `detail: "sha256:<hash>"` with neither remediation_hint nor retryable. No new option or param -- worker/codec.py has no error-path code. Refs ffroliva#896, ffroliva#897, ffroliva#898 (the data-layer root cause this surfaced, filed separately). * fix(worker): keep the media id on a failed queue row (ffroliva#895) The remediation this branch adds names the media id and tells the user to recover the clip with it. Over MCP that advice was unusable. `daemon.py`'s success branch records `flow_media_id` on the queue row; the failure branch did not. So a generation that finished, billed, and then lost its download left the row's `flow_media_id` NULL, and `gflow_generate_video`'s failed envelope carried no id at all. The agent's only copy was a UUID buried in an English sentence -- one that suggests a shell command an MCP client cannot run, while its actual tool, `gflow_download_media`, takes `media_id=`. `started_media_ids` is already in scope at that call site (it is passed to `record_failed_operation_safe` two lines above) and `update_task_status` already COALESCEs, so a task that failed before Flow named anything still writes NULL. Found by the ffroliva#896 design council reviewing this branch's blast radius, not by the tests -- no test covered a failure that occurs *after* on_started fires. Two now do: the fix, and a control proving None does not resurrect a stale id. * docs(895): commit the predict artifact and correct the ADR note PLAN.md and SCENARIO.md both cited the predict verdict at a `tmp/` path, which is gitignored -- a dangling reference for anyone reading the plan later. The artifact now lives beside them as PREDICT.md. The plan's ADR note also claimed Track B's second-callback design was compatible with both ADRs. Track B's own predict refuted that: ADR ffroliva#9 is satisfied, but ADR #2 / ARCHITECTURE.md:66 bars the DDD scaffolding, and that predict returned STOP. Correcting it in place rather than leaving a plan that vouches for a shape the council rejected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The labs.google NextAuth cookie expires on its own schedule, independently of the Google SSO session. When it did, REST calls failed with
AisandboxAuthError: no access_token in /fx/api/auth/session(exit 3) and the user had to click Flow's "Sign in" by hand - a click that needs no input, because Google still holds the SSO session.FlowApiClient._fetch_access_token now performs that round trip itself on the first missing (or non-JSON) session: NextAuth's csrf + signin/google API returns the Google OAuth URL, a temporary page follows it, and Google redirects straight back with a fresh session. Selector-free, so locale-invariant. One attempt per client, serialized across pooled pages; a password / 2FA / CAPTCHA / account-chooser page stops it at once and the existing exit 3 stands. Log event: auth.flow_session_refresh.
Live-verified (zero credits) on a copy of a real profile by tests/e2e/test_auto_relogin_e2e.py: clear the session cookie in a live context, get a fresh access token back, outcome=ok.
Summary
Validation
uv run python scripts/ci/check_doc_links.pyuv run ruff check src testsuv run pyright srcContribution Checklist
develop, unless it is a release or emergency fixSigned-off-by:(git commit -s)