Repository navigation
fix(migrated): name Flow's one-time upload-terms dialog instead of blaming the file (#719) - #757
Merged
Merged
Conversation
…aming the file #719 shape A. Flow holds an account's FIRST upload behind a one-time 'Rights to use this image' confirmation. It renders AFTER the chooser hands the file over — so _dismiss_dialog, which runs back in ensure_editor, never sees it — and Flow sends nothing until a human accepts it. The driver spent its full 60 s budget waiting for a request the page had already declined to make, then reported 'the upload never reached Flow or was dropped' and advised re-encoding the image. Neither the file nor the network was ever involved. Detected by COUNTING dialogs across the upload, not by matching one. The dialog's two buttons are button.flow-button-medium with no ligature and no data attribute, separable only by DOM order, and its copy is translated — there is no anchor there that satisfies this module's locale rule. 'A dialog appeared between the file being chosen and the wait expiring' is upload-related by construction, needs no selector, and cannot rot when Angular renames a class. The guard is in _upload_via_toolbar, where both i2v --initial-frame and r2v --ref already converge, so one check covers both surfaces. gflow does NOT click it, and there is no flag to make it: the dialog affirms that the ACCOUNT OWNER holds rights to what is uploaded, it is one-off per account, and the path already requires an interactive gflow auth login. A setting that could never safely default to on, for an event that happens once in an account's life, is a flag nobody sets. Tests: the terms branch, and a control proving a dialog that was ALREADY open does not get blamed — otherwise a lingering changelog modal (#26) would rewrite every unrelated upload timeout into a terms message. The plain-timeout test now also asserts the terms wording does not leak onto it. Refs #719
…not exist Found reading my own diff. The '#:' block was written as a Sphinx-style doc for an UPLOAD_CONSENT_DIALOG constant, and no such constant was ever defined — so it dangled between DIALOG_CLOSE and SUBMIT_RPCS, reading as documentation for whichever came next, and the guard's own comment pointed readers at a name that was not in the file. Nothing to rename: the guard COUNTS dialogs precisely so it needs no selector, so a constant would have been a name with nothing behind it. The rationale moves to the guard, which is where a reader meets the behaviour. Refs #719
…t the probe be the failure /code-review high on this branch before the PR. Two findings, both real, and the first is the #752 lesson repeating itself one surface over. 1. The dialog count runs INSIDE `except TimeoutError`, and it can raise. The likeliest reason no maseQ reply came is that the page died or navigated — in which case `count()` raises too, that escapes in place of MediaUploadRejectedError, and the run exits with an unmapped code 1 and a traceback instead of the mapped 27 with its remediation. Before this change the timeout path had no awaits and could not fail this way. The probe now degrades to the plain timeout: a probe is never the failure it reports. 2. The message asserted two things the code never observed. "no maseQ request was ever sent" was inferred from a RESPONSE listener, so a request that did leave and was answered slower than FRAME_UPLOAD_S produced it falsely — and that constant's own comment concedes a 20 MB file on a slow link needs longer. Fixed by watching the request too, which is worth more than the wording: `sent` now splits #719 shape A (nothing left the page) from shape B (it left and Flow did not answer) at the point of failure, and those two argue for opposite fixes — one for a retry, one against. The spike doc recommended exactly this; it costs one boolean. "Flow is holding the upload behind its one-time upload-terms confirmation" was asserted as fact from a bare dialog count, so any modal in that window inherited it — an error modal, a quota notice, a re-login prompt. It now says "most likely", and the remediation tells a user who sees a different dialog that THAT is the one blocking them. A count is evidence for a guess, not a fact, which is the whole thing this issue is about. Two new tests: the shape-B wording on a request that left and went unanswered, and exit 27 surviving a probe that dies mid-upload. The fake now models the upload request leaving the page and a page that dies once the file is handed over, so both states are constructible rather than argued. 90 unit tests in test_migrated_composer.py. Refs #719
KNOWN_ISSUES.md read: 'Affects: the legacy in-tree Compiled Growth worker, NOT gflow-cli itself', because 'gflow-cli's API-driven path bypasses this dialog entirely (the REST endpoint already implies acceptance)', with 'Workaround in gflow-cli: none needed.' True of the REST path, and false since the migrated driver started using the editor's OWN upload. So the entry a user hitting #719 would search for and find was reassuring them about the exact thing breaking their run — the failure mode this repo keeps correcting, in the document that exists to prevent it. Rewritten with the measurement (8 runs, 3 profiles), the one-off workaround, why gflow does not click it, and why there is no config flag. The old wording is quoted inside the new entry rather than deleted, because 'this used to say the opposite' is the part a returning reader needs. CHANGELOG entry covers the guard, the count-not-match decision and its locale rationale, and the product decision not to auto-accept. Refs #719
LIVE_VERIFICATION_v0.71.0 listed ci-probe as 'labs, freemium, no credit'. v0.70.0, one day earlier, listed it as migrated. Measured today with the account signed in: labs.google/fx/tools/flow on that profile redirects to flow.google.com/ — the one-way migration redirect. v0.70.0 was right. Worth more than a typo fix. This repo's own rule is that 'Flow's UI shows X' is not a fact until the host is named, so a mislabelled host in a verification ledger silently re-scopes every conclusion keyed to it. This one had a cost: a profile described as 'labs, freemium, no credit' made a credit-based theory for #719 look plausible for four runs, when the real cause was a one-time consent dialog that the account simply had never accepted. Could not be settled earlier today — the profile was signed out, and a signed-out browser redirects to the account chooser from either host. Refs #719
This was referenced Sep 8, 2026
Closed
Merged
ffroliva
added a commit
to arjhinety/gflow-cli
that referenced
this pull request
Sep 8, 2026
Two migrated-host error paths stop blaming the wrong thing (ffroliva#749/ffroliva#752, ffroliva#719 shape A). Both were found the same way — by asking what the app was SAYING, which this driver had never done; it only ever watched the wire. Release prep beyond the bump: - LIVE_VERIFICATION_v0.71.1 with the agent-mode A/B (run twice — review fixes landed after the first run, so it no longer covered the code) and the consent guard's six-run chain: guard fires while the dialog is up -> owner-authorised accept -> uploads -> fresh session uploads clean. The order was forced, because the dialog is one-off and accepting it destroys the failing state; the guard was therefore written and verified BEFORE anything was clicked, and its firing branch is now unrepeatable here. Every "not verified" item names a blocker and an issue. - PROJECT_STATUS: v0.71.1 current; v0.71.0 AND v0.70.0 demoted into <details> (the previous release left v0.70.0 inline). Three milestone rows added — the table's newest was v0.69.0, so two releases had never been recorded. - Six "unreleased line" claims dated from the CHANGELOG rather than guessed: ffroliva#716 -> v0.71.0, ffroliva#673 -> v0.69.0, ffroliva#493 -> v0.60.0, expand_prompt -> v0.22.0, and this release's own two -> v0.71.1. Four reviewers ran before the tag (/code-review, /ponytail-review, and the doc-review council's three auditors). Seven findings applied, all of them mine: - THE FALSE CLAIM, in two places. PROJECT_STATUS and INDEX both said the agent-mode recovery "already recovered as of v0.71.0's line" / "shipped working". It never shipped before 0.71.1 — `git show v0.71.0:...composer.py | grep agent` returns nothing — and KNOWN_ISSUES says the opposite three lines over. A v0.71.0 user reading the section an upgrade decision is made from would conclude their version self-recovers and file the drift bug this very release exists to prevent. Three reviewers found it; only one found the INDEX copy, which is the higher-traffic of the two. - Shape B shipped a NEW user-visible message with nowhere to look it up, and no remediation_hint. A user hitting it grepped KNOWN_ISSUES, landed on the consent entry, and was told the one-time accept fixes it forever — the same failure mode that entry's own retraction block calls out, reintroduced one shape over. Now has its own entry, a hint, and a test pinning both. - The ledger quoted a string the shipped build cannot emit (run 4 predates the review pass). Now quotes the shipped wording, with the history kept as a note. - r2v was missing from the migrated-host capability sentence in README, llms.txt, AGENTS.md and ARCHITECTURE while INDEX and KNOWN_ISSUES had it right — stale since v0.70.0, and conspicuous because this release's headline fix is scoped to r2v --ref. - A <details> opened for v0.68.0 at :196 and never closed, nesting every older release inside it. Tag counts balanced 51/51, which is exactly why the balance check I ran first missed it; verified now by DEPTH, which never exceeds 1. - The queue spike had zero inbound links repo-wide. check_doc_links cannot see that — it verifies outbound resolution, not reachability. Gates: all nine green. Full offline suite 4086 passed / 24 skipped with one local-environment false red (ffroliva#758 — the test clears the env var but Settings also reads .env); CI ran the identical code green on 3.11/3.12/3.13 in PR ffroliva#757. Transports suite re-run at 961 passed AGAINST THE WORKTREE'S OWN src — the first run silently imported the main checkout through the editable install, which is filed as ffroliva#760 because it means a release branch can validate code it is not shipping. Deferred and filed, not dropped: ffroliva#759 (ponytail — 81 of 233 added lines are comments restating the spike, and the queue spike ships an unrun download path). Not in this release: PR ffroliva#750 (migrated image generation) — branched pre-ffroliva#751 and conflicting across six files. ffroliva#719 stays OPEN for its second failure shape.
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.
Summary
Fixes #719 shape A. Builds on the instruments and findings in #754.
Flow holds an account's first upload behind a one-time "Rights to use this image" confirmation. It renders after the file chooser hands the file over — so
_dismiss_dialog, which runs back inensure_editor, never sees it — and Flow sends nothing until a human accepts it. The driver spent its full 60 s budget waiting for a request the page had already declined to make, then reported:…and advised re-encoding the image to strip metadata. Neither the file nor the network was ever involved. This blocked both
video i2v --initial-frameandvideo r2v --ref, which is every form that binds a local file on this host.The guard
It sits in
_upload_via_toolbar, wherei2v --initial-frameandr2v --refalready converge, so one check covers both surfaces.It counts dialogs rather than matching one, and that is the load-bearing decision. The dialog's two buttons are
button.flow-button-mediumwith no ligature and no data attribute, separable only by DOM order, and its copy is translated — there is no anchor there that satisfies this project's locale-invariance rule. "A dialog appeared between the file being chosen and the wait expiring" is upload-related by construction: it needs no selector, and it cannot rot when Angular renames a class.The
_default_remediation("re-encode the image to strip metadata") is not changed — it is still correct for the original #287 case, a 4xx on one file's metadata. It is overridden on this branch only.gflow does not accept the dialog, and there is no flag to make it
The dialog affirms that you hold the rights to the content being uploaded. That is not a claim a script may make on an account owner's behalf. It is also one-off per account, on a path that already requires an interactive
gflow auth login— so the manual step costs 30 seconds once in an account's life, and automating it would assert something on the user's behalf to save nothing.No config flag either: a setting that can never safely default to on, for an event that happens once, is a flag nobody sets.
Verification
Measured live on
ci-probe(#754 carries the instrument). The order was forced — the dialog is one-off, so the guard had to be written and verified against the live dialog before anything was accepted, or the failing state would be gone forever.no maseQ reply within 60s— the misleading messagea dialog opened after the file was chosen and no maseQ request was ever sent — Flow is holding the upload behind its one-time upload-terms confirmation(the pre-review wording; it is now hedged to "most likely" and backed by an observed request listener — see below)media_id 8914400f-…dialog: None, 4 POSTsThe dialog blocks the send (4), accepting unlocks it (5), it never returns (6).
Offline: 90 tests in
test_migrated_composer.py, fulltests/api/transportssuite, and the nine local gates./code-review highran before this PR, and found three thingsPer the process change from #752 — the gate goes before the merge, not after. All three are fixed in this branch:
The dialog probe could become the failure it describes. The
count()runs insideexcept TimeoutError, and the likeliest reason no reply arrived is that the page died or navigated — in which case the count raises too, escapes in place ofMediaUploadRejectedError, and the run exits with an unmapped code 1 and a traceback instead of the mapped 27 with its remediation. Before this change the timeout path had no awaits and could not fail that way. It now degrades to the plain timeout. Same lesson as Follow-up on #751: code review found 13 issues in the agent-mode fix, 2 of them real defects in the tests #752, one surface over.The message asserted two things the code never observed. "no maseQ request was ever sent" was inferred from a response listener, so a request that did leave and was answered slower than
FRAME_UPLOAD_Sproduced it falsely — and that constant's own comment concedes a 20 MB file on a slow link needs longer. Fixed by watching the request too, which is worth more than the wording:sentnow splits migrated host: an upload request leaves the page and Flow never answers (intermittent, ~1 in 4 on a consented account) #719 shape A (nothing left the page) from shape B (it left, Flow didn't answer) at the point of failure, and those argue for opposite fixes — one for a retry, one against. That is exactly what the spike doc recommended, and it costs one boolean."Flow is holding the upload behind its one-time upload-terms confirmation" was asserted as fact from a bare dialog count, so any modal in that window inherited it — an error modal, a quota notice, a re-login prompt. It now says "most likely", and the remediation tells a user who sees a different dialog that that is the one blocking them. A count is evidence for a guess, not a fact — which is the whole thing migrated host: an upload request leaves the page and Flow never answers (intermittent, ~1 in 4 on a consented account) #719 is about.
A
#:block documented anUPLOAD_CONSENT_DIALOGconstant that was never defined, so it dangled betweenDIALOG_CLOSEandSUBMIT_RPCSand the guard's comment pointed at a name not in the file. I caught this one myself reading the diff, before the review returned it. Nothing to rename — the guard counts precisely so it needs no selector — so the rationale moved to the guard.Four unit cases, and the last two came out of the review:
The fake now models the upload request leaving the page, and a page that dies once the file is handed over, so both states are constructible rather than argued.
On the Iron Law — why there is no new e2e for the positive branch
The guard's firing branch was verified live (run 4 above, against the real dialog), and that is the strongest evidence available for it. It is also unrepeatable: the dialog is one-off per account, and all three accounts on this machine have now accepted it. Reproducing the un-consented state needs a Google account that has never uploaded on
flow.google.com— a named external blocker, not a skipped step.The non-firing branch is where the regression risk actually lives (a guard that fires wrongly would mislabel every unrelated upload timeout), and that is covered offline by the control test plus the existing
e2e_videoi2v/r2v tests, which exercise the healthy upload path end to end.If a fresh account ever appears,
scripts/dev/spike_migrated_upload_wire.py(#754) reproduces the whole six-run sequence, including--accept-terms.KNOWN_ISSUES.mdcorrectedThe entry for this dialog said:
True of the REST path, and false since the migrated driver began using the editor's own upload — so the entry a user hitting #719 would find was reassuring them about the exact thing breaking their run. Rewritten with the measurement, the one-off workaround, and why gflow does not click it.
Scope
Not fixed here: #719 shape B — 1/4 on a funded account, the
maseQPOST goes out with 8 675 B, no reply arrives, and 2.6 s later the whole project-load rpcid inventory fires. Unrelated to consent, still open, and #719 stays open for it.MCP parity: no CLI leaf, option, queued-payload key or tool docstring claim changes. The guard is in the shared transport, so the MCP twin gets the improved error for free — same code path, no adapter change.