Known Issues¶
Living list of behaviour that's broken, surprising, or limited by design — alongside workarounds and a pointer to the issue / version where each is tracked or resolved.
Pair with CHANGELOG.md (what shipped per version) and DISCLAIMER.md (legal/scope limits).
Conventions¶
- Status: Open — still happens in latest release. Workaround listed.
- Status: Mitigated — partial fix in place; full resolution tracked.
- Status: Resolved — fixed in version
X.Y.Z; row kept here for searchability.
Open¶
One-time Flow banner/modal can cover the composer on first load¶
- Status: Open (#369)
- Severity: Low (transient) · Affected: any UI-automation command on a profile that hasn't seen the banner yet
A Flow-side one-time announcement banner/modal can overlay the composer and
make the mode-switch / settings probes miss. It shows once per
profile+cookie state, so it is gone on re-run and cannot be reproduced on
demand. gflow does not guess a dismiss selector for unknown overlays
(clicking unknown UI is riskier than failing). Since v0.43.0 the automatic
incident bundle records the overlay's bounded geometry, role,
aria-modal, z-index, pointer-events, and inner Material-Symbol
ligatures (no raw text) plus a sensitive/ screenshot — enough to write a
targeted dismissal once the next occurrence is captured. Workaround:
re-run the command; open the project once manually in Chrome to consume the
banner.
Profile lock reported as "held by another process" with no obvious owner¶
- Status: Resolved (#370) · remediation message clarified
- Severity: Low (by design; fails closed — never corrupts) · Affected:
ProfileLeaseacquisition (exit 11)
ProfileLockedError can surface when no chrome.exe / gflow process is
obvious. This is working as intended, not a stale-file bug. The profile
lock is a kernel advisory byte-range lock, which the OS releases the instant
its holder dies — so a leftover lock file can never block acquisition. If
acquire is blocked, a live process genuinely holds it, most often a
python.exe (a prior gflow run, or a pytest child that outlived its shell)
that a scan for chrome.exe misses. gflow deliberately never auto-deletes a
lock file or kills a recorded PID from metadata: an unlink→new-inode race could
put two browsers on one profile — the exact corruption the lease exists to
prevent. Since v0.43.0 contention reports the recorded owner's PID and observed
start time locally (metadata starts at offset 1 so Windows contenders can read
it while byte 0 is kernel-locked), and the remediation message now names the
live-owner reality and tells you to just retry when nothing is running.
Workaround: close the process at the PID the error prints
(Get-Process -Id <pid> / ps -p <pid>, then Stop-Process -Id <pid> /
kill <pid>), or use a different --profile.
Unexplained image-generation HTTP 400 (observed live 2026-07-22)¶
- Status: Open (no issue yet — evidence-gathering)
- Severity: Low (single occurrence) · Affected:
image t2iwire path
One live batchGenerateImages call returned an HTTP 400 that was neither a
content-safety rejection nor a known wire shape; the retry succeeded. Root
cause unidentified and not claimed fixed by v0.43.0. A 400 that resolves
on retry writes no incident bundle (successful commands capture nothing).
The diagnostics help only when such a 400 terminates the command as a
captured failure (e.g. a WireFormatError): its incident bundle's
network.json then carries allowlisted discovery evidence for the failed
request (numeric error code, status enum, known-key booleans, unknown-key
count, message length — never the raw body), which is the evidence this entry
is waiting on. Workaround: retry; the failure has not recurred.
Flow's uploadImage endpoint rejects some JPEGs with HTTP 400 (metadata-sensitive)¶
- Status: Open (#287)
- Severity: Low (typed + remediable) · Affected: i2v frame / reference uploads
Observed live 2026-07-11: one JPEG was rejected with HTTP 400 while
byte-identical-format siblings uploaded fine; re-encoding with
ffmpeg -q:v 2 -map_metadata -1 fixed it, implicating a metadata segment.
Since #290 the rejection raises MediaUploadRejectedError (exit 27) with that
remediation instead of a generic "Unexpected error." (exit 1). Workaround:
re-encode the file, or reference the already-in-project asset by media UUID
(--initial-frame <UUID> --project <id>). Root cause of Flow's metadata
sensitivity is unidentified.
i2v frame-slot picker selection by UUID — RESOLVED live 2026-07-11¶
290 routes i2v frame slots through _select_existing_asset (the UUID picker¶
live-proven in the Add-Media dialog: v0.26.0 i2i-by-UUID, #282 scroll
fixes). The frame-slot dialog carried a negative prior — #237's
name-search there never surfaced generated media (v0.25.0 rework) — but the
thumbnail-URL tile match succeeds where name search failed. Live evidence
(2026-07-11, my-profile): frame_ref_attached {slot: Start} → wire capture on
batchAsyncGenerateVideoStartImage with the asset's startImage bound →
SUCCESSFUL 720×1280 mp4; zero image_uploaded events (no duplicate
upload). Negative check: a foreign UUID exits 9 pre-generation with a
debug_frame_ref_miss_start.png screenshot. Caveat that stands: the asset's
project must be entered via --project, and projects that open in the
full-page media-library UI (#174) can fail earlier at mode_switch_trigger.
Video duration tab probe misses on the current Frames-submode DOM¶
- Status: Open (#288)
- Severity: Medium · Affected:
gflow videowith an explicit--durationon at least some Classic-profile cohorts (3/3 miss on a live 2026-07-11 run)
The duration_tab selector cascade ([role='tab']:text-is('4s') /
:has-text('4s')) fails to match on Flow's current Frames-submode settings
panel. Pre-fix this silently produced a clip of Flow's default length; since
the #289 fix the run fails fast with UiSelectorDriftError (exit 23) and a
debug_no_duration_tab.png screenshot — live-verified 2026-07-11 on the
my-profile profile (aborted pre-submit, saving the 10-credit generation).
Workaround: omit --duration to accept Flow's default.
Root cause (confirmed live 2026-07-11, screenshot evidence on #288): the
duration control is ABSENT from this cohort's settings popover — the panel
renders mode tabs (Imagem/Video), sub-mode (Frames/Elementos), aspect
(9:16/16:9), count (1x–x4), and the model dropdown (Veo 3.1 - Lite), and
nothing else. The earlier locale hypothesis is refuted: the UI renders in
Portuguese and the sibling count tabs (1x/x2) match fine; there is simply
no duration row to click. Whether Flow removed clip-length selection for this
cohort/model or moved it elsewhere is unknown — until that's answered,
--duration cannot be honored on affected cohorts and the fail-fast is the
correct behavior, not a selector bug to patch.
macOS: generation runs logged-out → HTTP 401, even with --browser chrome¶
- Status: RESOLVED in v0.23.0 (#222, fixed by #230, @gunalak)
- Severity: High · Affected: all
gflowgeneration on macOS with achrome-strategy profile; Windows was unaffected
On macOS, generation calls failed with HTTP 401 (AuthExpiredError at
project.createProject) on a profile that gflow auth login and verification
accepted. Two corrections fixed it (verified end-to-end on macOS Apple Silicon):
- Cookie-read bug (unconditional). The Flow cookie snapshot read the jar via
ctx.cookies(["https://labs.google"]), whose path-/filter silently dropped the/fx-scoped__Secure-next-auth.session-token. Now the full jar is read and filtered by domain, so the session token is captured. - macOS headed-context decrypt (intermittent). When the headed generation
context can't decrypt the on-disk store, the session is seeded from a snapshot
captured pre-launch via the working
--password-store=basicreader. No-op on Windows and on runs where the context decrypts natively.
Evidence: LIVE_VERIFICATION_v0.23.0.
Flow's new full-page media-library UI breaks entity attach (A/B rollout)¶
- Status: Open — Flow-side staged rollout; tracked in #174
- Severity: High · Affects:
gflow image t2i --reference-entityand movie R2V entity attach on accounts that received the new UI, any locale
Flow is A/B-rolling a new full-page media-library UI: clicking Add Media
in the composer navigates to a library page (sidebar: All media /
Characters / Scenes / Tools, with a floating quick-create composer) instead of
opening the resource-picker dialog. On affected accounts the right-click
include action still lands (a chip appears), but the staged entity never
reaches the submit — the request carries no referenceEntities, so the
submit backstops raise WireFormatError (exit 7) instead of silently
returning a text-only generation as success.
Plain generation on this cohort now fails cleanly (#183). When a project
opens into this full-page library (or the agentic chat composer) there is no
classic crop_* aspect/mode control, so gflow image/gflow video can't drive
generation. Rather than the old opaque UiSelectorDriftError "file a bug", the
mode-switch raise site now runs a runtime DOM scan and raises a clear, retryable
FlowAgentUiError (exit 25, "this cohort flaps; retry shortly"), and dumps a
DOM-signature diagnostics artifact (diag_mode_switch_miss.json + a full-page
screenshot) for reporting. The cohort is server-assigned per page load and flaps
within ~12h, so a re-run often lands the classic UI. Driving the new UI directly
is still out of scope.
How to tell which UI your account has: in the Flow web editor, click Add Media — a small dialog means the old (working) UI; a navigation to a full-page library means the affected new UI.
Note: the experiment appears to flap — the affected account observed on 2026-06-12 00:13 was back on the old dialog UI by 12:48 the same day (variant probe, issue #174). If you hit exit 7 on entity attach, re-running later the same day may simply work again.
Workaround: none yet on affected accounts — the attach gesture for the new UI is being reverse-engineered (recon plan in docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md). If you have a second profile/account still on the old UI, entity attach works there. Follow #174 for status; please report whether your account shows the dialog or the full-page library (plus your locale) on that issue.
4K image upscale requires a Flow Ultra subscription¶
- Status: Open (by design — a Flow platform limit, not a gflow bug)
- Severity: Low · Affects:
gflow image upscale --scale 4kon non-Ultra accounts
gflow image upscale <mediaId> --scale 4k returns exit code 22
(UpscaleUnavailableError) on accounts below the Ultra tier — Flow gates 4K
upscaling behind Ultra (the web UI shows an "Upgrade" button instead of a 4K
option). The account tier is reported on the wire as userPaygateTier but is
enforced server-side, so gflow cannot grant 4K locally.
Workaround: use --scale 2k (available on all tiers), or upgrade the Flow
account to Ultra. If you just upgraded, re-run gflow auth login --profile <name>
to refresh the session before retrying 4K. Wire detail:
docs/IMAGE_UPSCALE_RECON.md (#171).
Image generation returns HTTP 401 — aisandbox-pa generation endpoint¶
- Status: RESOLVED in v0.7.0 — moved to Resolved section
- Severity: ~~High~~ · Was-affecting: v0.6.0a6 and earlier
Resolution (2026-05-20, v0.7.0): the production
ui_automationtransport drives the Flow web UI so Flow's own JS issuesbatchGenerateImageswith full auth context — bypassing the 401 on theaisandbox-paHTTP path entirely. Live-verified end-to-end on theffrolivaprofile across four aspect ratios (9:16,16:9,1:1,4:3); seedocs/LIVE_VERIFICATION_v0.7.0.md. The 401 still hits the experimental HTTP transports (evaluate_fetch/bearer/sapisidhash) undersrc/gflow_cli/api/transports/experimental/— those are not the production path. Historical detail preserved below for searchability.
Image generation calls fail with HTTP 401 even on a profile that holds a
fully verified Flow session. Discovered 2026-05-17 while building the e2e test
suite, against a profile probed immediately after a successful
gflow auth login (auth_flow_session_verified, [OK] Flow session verified).
What works vs. what fails — on the same freshly verified profile:
| Operation | Endpoint | Result |
|---|---|---|
verify_flow_session |
labs.google/fx/api/auth/session |
✅ AUTHENTICATED |
FlowApiClient.health_check() |
Flow page context | ✅ True |
create_project |
labs.google/fx/api/trpc/project.createProject |
✅ 200 |
| image generation | aisandbox-pa.googleapis.com (private API) |
❌ HTTP 401 |
The evaluate_fetch transport receives a 401 on the generation request, runs
its refresh path (refresh_auth() re-navigates to the Flow URL), retries once,
gets 401 again, and raises:
from src/gflow_cli/api/transports/experimental/evaluate_fetch.py
(_handle_response). Call chain: FlowApiClient.generate_image /
generate_images_batch → _drive_image_generation → transport.generate_images
→ _generate_images_inner → _handle_response.
Distinct from issue #15. Issue #15 was a 401 on create_project caused by
the profile being signed in to Google but not the Flow app — fixed on the
fix/issue-15-i2v-bearer-auth branch by verifying the real Flow session at
login. That fix is confirmed working: create_project now succeeds. This is
a different 401 — it occurs on a profile that is verified and can create
projects, specifically on the aisandbox-pa.googleapis.com generation
endpoint, a different surface from the labs.google tRPC API.
Related — L0 aisandbox Bearer auth (
feature/scene-add-clip, 2026-05-31): theaisandbox-pa401 is NOT a SAPISIDHASH issue — live verification proved the real header isAuthorization: Bearer ya29.<oauth>(the SPA's OAuth2 access token, fetched fromGET /fx/api/auth/session).page.request401s because it sends cookies but not that token. Thegflow scenegroundwork now fetches+caches the token and attaches the Bearer to thepage.requestREST path (_post_json/_patch_json, host-scoped toaisandbox-pa) souploadImage/scenes/commitauthenticate — seedocs/superpowers/plans/2026-05-31-l0-bearer-pivot.md. Live-verified 2026-05-31: RESTuploadImagereturns 200 (tests/e2e/test_aisandbox_auth_live.py, credit-free). Liberating follow-up: the same Bearer likely unlocks theevaluate_fetchgeneration 401 above (and REST generation generally, modulo reCAPTCHA) — deferred, not yet applied to that transport.
Scope. The 401 affects every image-generation path uniformly on the
evaluate_fetch transport (the live one): test_e2e_single_image_gen (C2,
pre-existing), test_e2e_generate_image_without_project_id (PR #20,
pre-existing), and the dropped test_e2e_generate_images_batch_without_project_id.
It is not caused by recent test changes — test_transports_e2e.py is
self-described scaffold ("Task D.1 scaffold; Task D.2 drives the real
execution") that was never run green, and PR #20's e2e tests were merged
without live execution. Whether the production CLI (gflow image t2i /
gflow video i2v) is equally affected is unconfirmed — it uses the same
FlowApiClient + transport, so it very likely is, but that has not been
observed directly and should be checked first thing.
Experimental transports also broken. The bearer and sapisidhash
transports (api/transports/experimental/) fail before generation is even
reached: bearer cannot intercept an OAuth token (AuthExpiredError: bearer:
failed to intercept Bearer token from Flow page); sapisidhash cascades off
the resulting profile-lock contention. These are obsolete — only
evaluate_fetch is viable. Issue-#15 investigation notes had already
disproven the "bearer header" hypothesis for create_project.
Where to investigate.
- The login OAuth flow does request the
https://www.googleapis.com/auth/aisandboxscope (visible in the sign-in URL), so the account is authorized — the 401 points at how the credential is presented toaisandbox-pa, not at missing authorization. - Capture a real generation request from
evaluate_fetch— the exact URL, headers, and credential it sends — and compare with what the Flow web UI sends for the same action (browser DevTools network capture). - The
aisandbox-pa.googleapis.comhost may require a Bearer token: the issue-#15 "bearer header" hypothesis was disproven for thelabs.googletRPCcreate_projectroute, but may hold for this different Google API host. - Files:
src/gflow_cli/api/transports/experimental/evaluate_fetch.py(generate_images,_generate_images_inner,_handle_response,refresh_auth) andsrc/gflow_cli/api/client.py(_drive_image_generation).
Workaround: none known. Image generation against the live API does not currently succeed via the e2e transport path.
Browser session expires periodically — manual re-login required¶
- Status: Open · Severity: Medium · Affects: all versions · Tracked: N/A (architectural)
Google's web session cookies aren't permanent. They expire when: - Long stretch of inactivity (typically months) - You change your Google password - You sign out from another device's session manager - Google flags the session as suspicious (geo-jump, new device fingerprint)
When this happens, the next API call returns 401/403 and gflow-cli raises AuthExpiredError.
Workaround:
Re-running auth login reuses the existing profile dir (you typically just click "Continue as
Why we don't auto-refresh: Google's session-refresh flow can include CAPTCHA / device verification that only a human can complete. A community SDK can't reliably automate that step. See docs/AUTHENTICATION.md § Refresh / expiry.
Roadmap: not scheduled. The Phase 4 hardening pass (v0.4.0a2) added typed AuthExpiredError + exit code 3 so scripts can branch on auth expiry deterministically. A periodic "session liveness" check + a gflow auth refresh command are still candidates for a later phase, but not committed to a version yet.
Chromium cookie database locks block yt-dlp integrations (Instagram/restricted download paths)¶
- Status: Mitigated · Severity: Low-Medium · Affects: any downstream helper calling
yt-dlp(includingclaude-video,cg-decode/refanalyzer, orexperience-vault)
When executing yt-dlp from automated shell calls or Python wrappers while a Chromium-based browser (Chrome or Edge) is active, you may see:
yt-dlp to crash when attempting to extract cookies from the browser.
Mitigation & Workaround:
1. Cookie Export: Export Netscape-formatted cookies from your logged-in browser session to a static file (e.g. ig_cookies.txt or .auth/ig_cookies.txt).
2. Configuration Isolation: Prevent yt-dlp from reading global user configurations that might force browser-cookie loading (e.g. %APPDATA%\yt-dlp\config.txt containing --cookies-from-browser chrome).
- In CLI calls, pass --no-config (or --ignore-config):
YoutubeDL constructors, pass "ignoreconfig": True in the options dictionary:
Flow's first-upload terms-of-use dialog ("Aviso") blocks the worker (worker-only)¶
- Status: Open · Severity: Low · Affects: the legacy in-tree Compiled Growth worker, NOT
gflow-cliitself
Flow shows a one-time "Aviso" / "Notice" terms-of-use confirmation on the first image upload of a new account session. The legacy Playwright worker has to explicitly click "Concordo" / "Agree". gflow-cli's API-driven path bypasses this dialog entirely (the REST endpoint already implies acceptance).
Workaround in gflow-cli: none needed.
Workaround in legacy worker: see Compiled Growth's flow_video.py consent-dismiss block.
Flow's release-notes ("What's new") changelog popup blocks first-run UI automation¶
- Status: Mitigated · Severity: Medium · Tracked: #26
Google Flow ships a release-notes / "What's new" iframe (changelogs/YYYY-MM-DD-...html) the first time a logged-in profile visits the page after a Flow deployment. The iframe sits on top of the editor and intercepts pointer events on Flow's own controls — Playwright finds the right selector but cannot click it because the changelog is in the way. Issue #26 confirmed the same iframe also blocks the settings menu after project navigation.
Symptom:
playwright._impl._errors.TimeoutError: Locator.click: Timeout 30000ms exceeded.
- <iframe ... src="https://www.gstatic.com/.../changelogs/...html"></iframe> ... intercepts pointer events
- retrying click action (57 retries, then timeout)
Mitigation: UiAutomationTransport._dismiss_blocking_overlays(page) detects Flow changelog iframes (iframe[src*='/flow/changelogs/'], iframe[src*='/changelogs/']) and dismisses them via a close-button selector cascade with an Escape-key fallback. Invoked after _enter_editor (image flow) and after _wait_video_editor_ready (video flow) so downstream clicks aren't intercepted. Structured logs identify what was dismissed; a debug screenshot is captured if dismissal cannot be confirmed.
Legacy workaround (no longer required): open Flow in Chrome with the same profile once, click the X on the "What's new" popup, then close Chrome cleanly.
No in-CLI quota visibility¶
- Status: Open · Severity: Low · Roadmap: v0.5
gflow-cli doesn't yet show how many Veo / Imagen credits remain on your Ultra/Pro subscription. You can check at https://gemini.google/subscriptions/ in the meantime.
Roadmap: v0.5 will surface remaining quota via gflow auth status once we capture the relevant Google API.
gflow serve / MCP worker queue: interrupted post-submit tasks need manual reconciliation¶
- Status: Open (by design — no automated reconciliation) · Severity: Low · Affects:
gflow servedaemon and MCP worker paths only (not the plain CLI, which has no queue)
The worker queue (generation_queue, used by the gflow serve daemon and MCP tool calls) checkpoints every task's progress through claimed → submit_attempted → remote_started → terminal. If a task is interrupted (daemon restart, crash, or cancellation) while still claimed (before any submit), gflow-cli safely marks it failed — nothing was spent, safe to retry. But if it's interrupted at submit_attempted or remote_started — after the credit-spending submit click may have fired — it is marked indeterminate instead: a credit may have been spent and the outcome is unknown. An indeterminate task is never silently reported as failed and never auto-resubmitted (that could double-spend a credit for one generation).
Why this can't be resolved automatically today: Flow's generation-status REST endpoint rejects a bare, cookie-only page.request re-check with HTTP 401 — only a live, authenticated Playwright SPA re-poll (opening the project page and reading the DOM) can turn a preserved handle back into a real status, and that live re-poll path is not wired into recovery yet (tracked for a future phase; see the C1 handle-spike notes in docs/superpowers/specs/2026-07-19-production-readiness-hardening-design.md Appendix A).
Decision (2026-07-21): keep the safe stub — recover_processing marks post-submit interruptions indeterminate and never auto-resubmits, which is already correct. Building the auto-reconciler is deferred until F1 (credit-free project-page re-entry) is confirmed against live Flow; until then a blind reconciler would be speculative code against unverified blackbox behavior. The client reconcile-hook seam and the live-gated tests/e2e/test_crash_recovery_e2e.py are already in place for when F1 lands.
Manual reconciliation: an indeterminate row's checkpoint retains whatever handle/project info was captured before interruption. To resolve one by hand: open the relevant Flow project in the browser and check whether the expected asset appears.
- Found — the generation completed; no action needed (the credit was spent as intended, just not auto-recorded locally).
- Not found after a few minutes — it likely never completed; safe to resubmit the same prompt manually.
Queue payload schema (V0/V1): the queue's payload_json carries an additive schema_version key. A payload with no schema_version key is legacy V0 and decodes with the same field lookups as V1 (the shape is otherwise identical) — both remain readable. The codec always writes the current version (1) on any re-encode. Any other version is treated as unknown and rejected with QueueSchemaError (exit code 30) rather than interpreted optimistically — this is a fail-closed guard against decoding a payload written by an incompatible future (or hand-edited) version.
Aspect-ratio support depends on the Veo / Imagen model version¶
- Status: Open · Severity: Low
Currently confirmed:
- Veo I2V: 9:16, 16:9, 1:1
- Imagen: 1:1, 9:16, 16:9, 4:3, 3:4
Other ratios may be silently rejected or coerced server-side. We validate in the CLI to whitelisted values to fail fast.
REST API 401 — all aisandbox-pa.googleapis.com generation endpoints blocked¶
- Status: RESOLVED in v0.7.0 — image generation live-verified end-to-end
- Severity: ~~High~~ · Was-affecting: v0.2.0a1 through v0.6.0a6
Resolution (2026-05-20, v0.7.0):
UiAutomationTransportdrives the Flow editor so Flow's own JS issues every generation request with full auth context — image generation now succeeds end-to-end (seedocs/LIVE_VERIFICATION_v0.7.0.md). Video T2V works at the library level via the same transport (Phase A, PR #23); CLI wiring (gflow video t2v/i2v/batch) is queued for Phase B. The HTTP transports underexperimental/remain blocked by this 401 by design — they are not on the production path.
Even with a valid browser session (cookies present), calling Flow's REST API directly via fetch or page.request against aisandbox-pa.googleapis.com returns HTTP 401. This blocks all generation routes:
| Endpoint | Status |
|---|---|
flowMedia:batchGenerateImages (image gen) |
❌ 401 |
video:batchAsyncGenerateVideoText (T2V + I2V) |
❌ 401 (confirmed 2026-05-18 e2e run) |
flow/uploadImage (image upload for I2V) |
❓ untested (blocked before reaching this step) |
video:batchCheckAsyncVideoGenerationStatus (status poll) |
❌ 401 (confirmed 2026-05-19 — even via page.request.post from the authenticated browser context) |
The Phase 0 video spike (2026-05-19) confirmed the generation routes do
succeed when driven through the UI (Flow's own JS issues them) — but the
status-poll route 401s even from page.request.post inside the authed
page. Polling must therefore capture Flow's own status responses, not issue
the request directly. See the video-generation design spec §10.5.
project.createProject (on labs.google/fx/api/trpc) does work — it uses a different domain and auth model.
Root cause: Google's backend has tightened security on aisandbox-pa.googleapis.com, requiring a browser fingerprint, Origin/Referer headers, and reCAPTCHA token that raw script-driven requests cannot provide.
Workaround: Use the UI Mimicry approach — drive the Flow editor by clicking real buttons so the browser itself issues the generation requests with full auth context.
Roadmap: v0.6.0a5 will add video generation (T2V + I2V) to the UiAutomationTransport, making it the single transport that covers both image and video generation. I2V requires driving the Flow UI's image-upload button so the browser calls uploadImage with its own session cookies.
Output dir is not tidied automatically¶
- Status: Open · Severity: Low · By design
gflow-cli never deletes from $GFLOW_CLI_OUTPUT_DIR. Generated assets accumulate forever unless you clean them up.
Workaround: schedule a cron / Task Scheduler job, e.g.:
batchGenerateImages HTTP 403 — WAF / reCAPTCHA PUBLIC_ERROR_UNUSUAL_ACTIVITY¶
- Status: Open · Severity: High (blocks affected profile until WAF score decays or profile is replaced)
- First observed: 2026-05-23 on profile
my-profileduringgflow image batchruns - Surfaces as:
gflow_cli.errors.WafRejectionError: WAF rejection (HTTP 403): batchGenerateImages HTTP 403 — reCAPTCHA score too low or WAF fingerprint mismatch - structlog signature:
ui_automation.batch_response_seenwithstatus=403followed byui_automation.batch_403_bodycontaining'message': 'reCAPTCHA evaluation failed', 'status': 'PERMISSION_DENIED', 'reason': 'PUBLIC_ERROR_UNUSUAL_ACTIVITY'
Distinct from the historical aisandbox-pa 401 (resolved in v0.7.0). The 403
here means Flow accepted the session as authenticated but reCAPTCHA Enterprise
scored the request as bot-like and blocked the generation call. The my-profile
profile reproducibly 403s on batchGenerateImages even after a fresh
gflow auth login --browser chrome; the same code path on profile ffroliva
(re-authenticated the same day) succeeded end-to-end across one t2i + a 4-image
batch — so it is not a global incompatibility but a per-profile WAF state.
Likely contributing factors: - Repeated automated runs on the same profile within a short window - Playwright-driven Chrome leaks small fingerprint differences vs. unautomated Chrome that reCAPTCHA Enterprise can score - The image-batch path issues several rapid-fire requests after the count-tab clicks, which the scoring may treat as a single fast burst
Workarounds:
1. Use a profile with lower WAF heat — re-test on a different Chrome-strategy
profile (gflow auth login --profile <new> --browser chrome). The profile
that has been driven by recent automation runs is usually the hottest.
2. Let the WAF score decay — typically hours to a day. Manually using real
Chrome on the same account in between can help (real interactions lower
the score).
3. Avoid same-day repeated batch runs on a profile after a 403 — each
rejected request can raise the score further.
4. Widen the submission jitter when composing multiple runs — --jitter 10-30
or GFLOW_CLI_JITTER_RANGE (#241); cadence guidance in
DEBUGGING § WAF cadence.
Tracked separately from the architectural "first-attempt listener-miss flake" — that one was caused by editor mode confusion and is resolved by PR #40. WAF 403 is a fresh, distinct issue and not blocked by any code change in this repo.
UiAutomationTransport selectors locale-agnostic — issue #24 Phase 5 complete¶
- Status: Resolved (pending owner live e2e on non-EN profile) · Severity: Low · Tracking: issue #24, issue #94, issue #170
--lang=en-US removed in PR #127 (2026-05-30). All selector groups now use
locale-stable anchors: IMAGE_MODEL_OPTION_SELECTORS and
VIDEO_MODEL_OPTION_SELECTORS converted to dict[Model, tuple[str, ...]]
cascade structure; branded product names ("Nano Banana 2", "Nano Banana Pro",
"Imagen 4", "Veo 3.1 - *", "Omni Flash") are confirmed locale-stable
Google-branded identifiers. Locale is controlled by the locale=locale_env
Playwright kwarg (persists across all in-session navigations). Full resolution
gate: live e2e with gflow image t2i (each model) on a non-EN Chrome profile.
2026-06-12 correction (issue #170): the "all selector groups" claim above
had two stragglers — PICKER_INCLUDE_BUTTON and PICKER_CONTEXT_INCLUDE
hardcoded the pt-BR caption "Incluir no comando", breaking
--reference-entity (image t2i), movie R2V entity attach, and Vozes voice
attach on every non-Portuguese account (Flow renders in the ACCOUNT language;
?hl=en cannot override it). Fixed by converting both constants to sequential
tier cascades — context-menu Tier 1 is the locale-free add-ligature menuitem
scoped to the open menu; the Vozes button has no ligature, so pt/ru/en text
leads with a lone-iconless-dialog-button structural fallback. The matched tier
is emitted as ui_automation_video.include_selector_tier (drift telemetry),
exhaustion raises typed TransportTimeoutError (exit 9) with a locale-neutral
remediation hint, and an image-side submit backstop now raises
WireFormatError when a requested entity never rode the wire.
Phase 2 progress (2026-05-25, develop / post-v0.8.1, unreleased):
ONBOARDING_SELECTORSrestructured — replaced the original 9 English/PT-BR text-only entries with a two-tier cascade:_ONBOARDING_STRUCTURAL_SELECTORS(3 strict entries) — locale-free ARIA/ID anchors:button#L2AGLb(Google Funding Choices SDK stable ID) plus exact ARIA-label matches (Accept all,I agree). These are programmatic SDK constants, not UI strings._ONBOARDING_TEXT_SELECTORS(~37 entries) — leads with two case-insensitive ARIA-partial entries (aria-label*='Accept' i/*='Agree' i) that catch many CMP dialogs (OneTrust, Cookiebot) whose aria-label values stay in English even on non-EN pages, followed by:has-text()selectors covering 14 locales: EN, PT, DE, ES, FR, IT, NL, JA, ZH, KO, PL, RU, TR, ID. The ARIA-partial entries live in this tier because English aria-label values are not guaranteed across every CMP.-
ONBOARDING_SELECTORS = (*_ONBOARDING_STRUCTURAL_SELECTORS, *_ONBOARDING_TEXT_SELECTORS)so structural entries are always tried first. Cascade-ordering invariant is verified byTestBypassOnboardingintests/api/transports/test_ui_automation.py. -
_attach_frame(I2V/R2V frame slots) flipped to structural-first — was English text-label first (FRAME_SLOT_BY_LABEL) with structural fallback; now triesFRAME_SLOTS_STRUCTfirst, falls back toFRAME_SLOT_BY_LABELonly when structural count is insufficient. Slot selection unit tests live inTestAttachFrameSlotSelection(tests/api/transports/test_ui_automation_video.py).
Correction (2026-05-26, issue #63): PR #70's original
FRAME_SLOTS_STRUCT = "div:has(> button:has(i.google-symbols:text-is('swap_horiz'))) > div[aria-haspopup='dialog']"
matched zero elements on real Flow DOMs — the swap_horiz icon uses class
material-icons (NOT google-symbols) and the slots are <div type="button">,
not children of any div > button wrapper. Production I2V therefore relied on
the English-text fallback and silently broke on non-EN profiles. Discovered
via DOM probe + LIVE e2e on ffroliva (de-DE → pt-BR effective). Replaced
with FRAME_SLOTS_STRUCT = "div[type='button'][aria-haspopup='dialog']" (a
unique pattern in Flow's editor). Also added a .first fallback for the
End-frame case — after Start is attached, only one structural slot remains
and the prior .nth(slot_index) went out-of-bounds. Both fixes shipped
together via #63.
-
GFLOW_CLI_LOCALE— Playwrightlocale=env override from PR #51 remains available (defaulten-US). -
--lang=en-USdependency reduced —ONBOARDING_SELECTORSand_attach_frameno longer require it. The arg is still passed because removing it requires a broader live-e2e sweep (I2V/R2V across multiple locales); it will be dropped once that completes. -
Live e2e on
de-DE(2026-05-25) —GFLOW_CLI_LOCALE=de-DET2V onffroliva(Pro) completed in 70.9 s and returnedMEDIA_GENERATION_STATUS_SUCCESSFULwith a 3.1 MB 1280×720 H.264 mp4 (8 s clip). Confirms the structural-first selectors andGFLOW_CLI_LOCALEenv override work end-to-end on a locale outside the original 9-entry English/PT-BR list. -
Live I2V e2e on
de-DE(2026-05-26, issue #63 closure) —GFLOW_CLI_LOCALE=de-DEI2V (Start + End frames) onffrolivaviatests/e2e/test_transports_e2e.py::test_e2e_i2v_start_end_frame_attachcompleted in 124 s and returned a terminalSUCCESSFULVideoResultwith a downloaded mp4 carrying validftypmagic bytes. The test asserts on theui_automation_video.frame_attachedstructlog event for both Start and End, proving the structural cascade resolved both slots without falling through to the EN-text tier. Note: Chrome's UI rendered in pt-BR despiteGFLOW_CLI_LOCALE=de-DE(env affects Playwright'sAccept-Language, not Chrome's profile language) — both are non-EN so the test still verifies the locale-leak fix.
Earlier — Phase 7 multi-image-prompt work addressed the count-tab selectors:
- _COUNT_TAB_TEXT_RE = ^(1x|x[2-4])$ only matches the digit+x format Flow
renders identically in every locale (numbers/symbols are not translated).
- _set_count falls back to positional .nth(count - 1) when the read-back
text is unrecognised — locale-invariant.
Earlier — PR #48:
- Added --lang=en-US Chromium launch arg; parts of NEW_PROJECT_SELECTORS /
SUBMIT_BUTTON_SELECTORS tails still match by English text (icon-first selectors
lead and cover the common path, so these are maintenance debt rather than
active blockers).
Remaining gate: live e2e with gflow image t2i --model <each> on a non-EN
Chrome profile (GFLOW_CLI_LOCALE=<non-EN>) to confirm model picker resolves
correctly without --lang=en-US. --lang=en-US has been removed (PR #127);
locale=locale_env Playwright kwarg provides locale continuity across navigations.
Workaround: with Phase 2 changes, most locales are handled automatically.
For locales outside the 14 covered by _ONBOARDING_TEXT_SELECTORS, ARIA-based
structural selectors fire first and cover Google's Funding Choices consent SDK.
For non-standard CMP dialogs not covered, prefer accounts whose Flow renders in
one of the 14 supported locales or in English.
t2v response can omit operation name — flow_operation_id persists NULL¶
- Status: Open · Severity: Low · Affects: v0.9.0+ (data layer)
The data layer's on_started callback captures
operations[0].operation.name from each batchAsyncGenerateVideoText
response and persists it as operations.flow_operation_id. This field
is best-effort, not guaranteed for any model: omni-flash's response
shape does not carry it, so omni-flash rows end up with
flow_operation_id NULL. Update 2026-07-21 (live): the same NULL
was observed on a live veo-lite t2v run (remote_started checkpoint
with operation_id=None), on an agentic-UI-cohort account — so veo-*
is not a reliable guarantee either; the original claim that veo-* rows
always carry the operation name was false. The rest of the row (prompt,
model, aspect, started/completed timestamps, batch ID, output paths) is
recorded normally regardless.
Impact: cosmetic/provenance-only — media_id is the canonical
handle (poll, download, and every CLI lookup use it, not
flow_operation_id). gflow data media <id> and provenance lookup by
Flow media ID still work. Nothing in the current CLI queries by
flow_operation_id; a future feature that did would miss rows on any
model whose response omits the operation name.
Workaround: none needed if you don't query by flow_operation_id.
Roadmap: capture response samples across models (including veo-lite)
where the operation name is absent, identify the equivalent provenance
handle (if any), and either map it into flow_operation_id or document
that these cases legitimately have no such identifier. Track via a
follow-up issue once samples are captured.
gflow video chain re-exposes the i2v→t2v silent-route risk (issue #125)¶
- Status: Mitigated · Severity: Medium · Affects:
gflow video chain(v0.12.0)
Every chain link after the first is an image-to-video (I2V) generation seeded by
the previous clip's last frame. The same silent-route defect that affects
gflow video i2v (issue #125)
applies here: if the chosen model can't do i2v interpolation, Flow drops the
seed frame and routes the request to the plain text-to-video endpoint
(batchAsyncGenerateVideoText) — burning a credit for a text-only clip that
breaks continuity, with no error from Flow.
Mitigation (two layers):
1. Model pin. omni-flash (the only model known to silently drop frames) is
removed from the chain --model choices, and the orchestrator rejects any
model whose supports_i2v_interpolation() is false before any spend
(ModelModeIncompatibilityError, exit 17).
2. Per-link wire-route abort. For each seeded link the transport inspects the
captured generate-response URL; if it observes batchAsyncGenerateVideoText
for an i2v link it raises WireFormatError (logged
ui_automation_video.i2v_routed_to_t2v, issue #125) rather than reporting a
fake success. The chain aborts and preserves every link completed before the
failure (ChainPartialError).
Workaround: stick to the Veo 3.1 models (veo-lite / veo-fast /
veo-quality / veo-lite-lp); these are the only accepted chain models.
gflow video chain continuity caveat — black / fade-out final frame¶
- Status: Open · Severity: Low · Affects:
gflow video chain(v0.12.0)
Chain seeds each link with the last frame of the previous clip. If a clip fades to black (or to a near-empty frame) at its very end — common with cinematic prompts — the extracted seed frame is mostly black, so the next link starts from black and continuity visibly breaks.
Workaround: pass --seed-offset MS to extract the seed frame a few hundred
milliseconds before end-of-file, skipping the fade. For example
--seed-offset 200 seeds from 200 ms before EOF. Tune per the fade length of
your prompts.
gflow video chain outputs N clips, not one file — auto-concat is deferred¶
- Status: Open (by design) · Severity: Low · Affects:
gflow video chain(v0.12.0)
A chain produces N separate mp4s (one per link), not a single stitched
video. Auto-concatenation is deferred: Flow's server-side concatenation
(runVideoFxConcatenation, used by gflow scene) is project-scoped — every
clip must live in the same Flow project to be concatenated. But chain links are
generated sequentially and generation cannot pin all links to one shared
project, so there is no clip set the concat endpoint could combine at the end of
a chain run.
Workaround: stitch the link clips into one file with gflow scene
(server-side, credit-free, no ffmpeg) after the chain completes. The chain
prints its chain_id and a reminder to do this on success.
Roadmap: wiring chain links into a single Flow project so the chain can auto-concat its own output is under consideration — tracked as backlog.
gflow video chain --resume-from re-seeds the first resumed link as T2V¶
- Status: Open · Severity: Low · Affects:
gflow video chain(v0.12.0)
--resume-from <chain-id> skips links already paid for in a prior run (they are
not re-billed) and continues from the first incomplete link. However, the
first resumed link is generated as a text-to-video link, not seeded from the
last frame of the last completed link — there is no cross-run seed-frame
hand-off yet. The resume is credit-safe (no double-billing), but visual
continuity restarts at the resume point: the first resumed clip will not flow
seamlessly from the clip before it.
Workaround: if seamless continuity across a resume boundary matters, re-run
the affected tail of the chain from scratch rather than resuming, or stitch with
gflow scene and accept the cut at the resume boundary.
Roadmap: persist and re-extract the boundary seed frame so a resumed link can continue as a seeded I2V generation — tracked as backlog.
Mitigated¶
Auth verification depends on Google's NextAuth session endpoint¶
- Status: Mitigated · Severity: Low (degrades fail-closed) · Affects: issue #15 fix onward · Tracked: issue #15
gflow auth login verifies a real Flow sign-in by calling
https://labs.google/fx/api/auth/session (see src/gflow_cli/auth/verification.py)
and by checking for the Google SAPISID cookie. These are external Google
surfaces — if Google changes the endpoint path, the response shape, or the
cookie names, verification degrades fail-closed: it reports
VERIFICATION_ERROR (an honest "could not verify") rather than a false
success. The expected authenticated response shape is pinned by the
AUTHENTICATED_BODY fixture in tests/auth/test_verification.py — a Google
change surfaces there as a failing test. Start any investigation of a sudden
gflow auth login verification failure at that fixture and verification.py.
Since PR #168 the production entry point is verify_flow_profile, which reads
the session cookie directly from Chrome's SQLite store via browser_cookie3
(a no-browser fast path) and only falls back to launching Playwright when that
decryption fails. This adds two more local surfaces to check when verification
fails unexpectedly: a Windows DPAPI decrypt failure (cross-user / cross-machine
key — surfaces as a RuntimeError that auth/cookies.py normalizes to
PermissionError to trigger the Playwright fallback) and a locked cookie DB
(Chrome still running holds an exclusive SQLite lock). Both degrade fail-closed.
Same profile can't be used in parallel¶
- Status: Mitigated (crash → typed fail-fast rejection) · Severity: Low · Affects: all versions
Chromium refuses to open two persistent contexts on the same user-data-dir simultaneously. Historically this surfaced as an unhelpful Chromium "ProcessSingleton: profile is locked" error partway through a run. As of the profile-lease hardening (production-readiness plan, slice D1/D3), gflow-cli enforces this itself: a cross-process advisory lock (ProfileLease, kernel flock on POSIX / msvcrt.locking on Windows) guards every profile directory. A second gflow invocation, gflow serve daemon task, or MCP call against an already-leased profile is rejected immediately — before any Chrome process starts — with a typed ProfileLockedError (exit code 11); it never waits and never silently corrupts the profile.
Workaround: use different profiles for parallel work — different profiles acquire independent leases and run fully concurrently.
# Terminal 1
gflow image batch ./batch-a.tsv --profile work
# Terminal 2 — different profile, same time, OK
gflow image batch ./batch-b.tsv --profile personal
Roadmap: Phase 4 (v0.4.0a2) added a per-worker Page pool on one shared BrowserContext (GFLOW_CLI_CONCURRENCY=N), intended to let one gflow-cli process fan out multiple in-flight generations, but no current CLI command drives more than one generation at a time through it — the only feature that did (a manifest-driven video batch runner) never worked and was removed. Cross-process same-profile serialization remains a hard constraint (Chromium can only own one persistent context per user-data-dir) — but it is now a clean, typed, fail-fast rejection instead of an unstructured crash. Multiple shells against the same profile remains a "use different profiles" workaround; there is no queueing/waiting mode.
Resolved¶
gflow video's manifest-driven batch subcommand didn't skip already-completed entries — RESOLVED as obsolete¶
- Status: Resolved (obsolete) · Severity: Was-Medium · Was-affecting: v0.2.0a1 through the command's removal · Fixed in: n/a — removed in production-readiness hardening (see
fix: remove nonfunctional video batch command)
This entry described a gap in gflow video's batch subcommand: it never
maintained a local manifest-of-outputs, so rerunning a partially completed
TSV manifest after a mid-run failure would re-submit already-rendered rows
and could burn additional credits. The command itself, however, never
actually worked end-to-end — it always exited with a stub error before
reaching Flow — so no run could have been "partially completed" in the
first place. It has since been removed entirely as a nonfunctional stub.
The underlying command no longer exists, so this gap is moot rather than
fixed; kept here for searchability. gflow image batch (the real, working
batch command) is unaffected. For video, loop gflow video t2v/i2v from
the shell — see docs/USAGE.md § Batch video generation (shell loop).
False "forced agentic — not recoverable" aborts from an icon-heuristic cohort probe¶
- Status: Resolved · Severity: Was-Medium (spurious exit-25 aborts on profiles that could in fact reach classic mode) · Was-affecting:
--ui-mode classicand classic-only operations through v0.37.0 · Fixed in: 0.38.0 · Tracked: #299, #332
The forced-agentic detection keyed on UI icons including apps_spark_2 — which is
Flow's Tools button, present in BOTH cohorts — so a classic-capable profile could
be misclassified as an unrecoverable agentic cohort and abort with exit 25. Recovery
was a blind single click on the Agent pill with no state verification. v0.38.0
replaces this with a state-aware mode controller (mode_control.py) that reads the
Agent toggle's aria-pressed attribute (false = classic media, true = agent —
locale-invariant), closes the expanded chat sidebar first, toggles off only when
actually in agent mode, and re-verifies. Live-verified with a full
classic→agent→classic round-trip and a real agentic→classic recovery in the v0.38.0
release run (evidence).
Agentic image generation could silently attribute a pre-existing project asset as the "generated" image¶
- Status: Resolved · Severity: Was-High (wrong file downloaded and reported as success, silently) · Was-affecting: the agentic driver through v0.30.0 (and, for the pre-download guard below, every transport) · Fixed in: unreleased (0.31.0) · Tracked: #281; related picker fix #282
Discovered 2026-07-10 in a live production run: gflow image t2i under the
agentic driver downloaded an old project logo and reported it as a freshly
generated "portrait" — no error, just the pre-existing warn-only line
"Generated media was saved, but local history was not updated." — and 11
downstream i2i scenes were then anchored to the wrong file.
Root cause — two defects in await_images (agentic driver):
1. The new-media baseline was a single DOM scrape (_scrape_img_srcs); a
pre-existing tile that rendered lazily (after generation had already
started) was missed by that scrape and then counted as "new" once it
appeared.
2. _build_generated_images sliced an unordered UUID set down to
expected_count — an arbitrary pick when more "new" UUIDs were present
than were actually requested, with no signal for which UUID(s) belonged to
this generation.
A third, independent gap existed downstream of the driver: even a transport
that never hits the agentic DOM-scrape path above could still hand back a
flow_media_id already present in local history for the profile. That case
was caught by the generic DataStoreError path, which only warned and
continued — a silently duplicated/misattributed history row, not a hard
failure.
Fix — three defense layers, all raising the new MediaAttributionError
(exit code 26, RFC 9457 type media-attribution) instead of a silent or
warn-only outcome:
1. Baseline settle. The baseline is now the union of two
_scrape_img_srcs passes one poll interval apart, absorbing lazy-render
stragglers before they can be mistaken for new media.
2. Ambiguity fail-fast. If more new UUIDs still appear than were
requested, await_images raises MediaAttributionError naming every
candidate UUID and the expected count, rather than guessing via
_build_generated_images's old arbitrary slice.
3. Pre-download attribution guard + collision escalation.
OperationRecorder.verify_media_attribution() (called from cli_image.py,
image_batch.py's manifest batch path, and the worker daemon's
FlowWorker.process_task; consolidated onto the recorder in #283 after
shipping as three near-identical module-level copies) checks
is_media_recorded() and raises before any download if the driver
returned a flow_media_id already recorded for the profile.
Separately, a DataIntegrityError from the recorder's
UNIQUE(profile_name, flow_media_id) constraint now escalates to
MediaAttributionError (naming the suspect file) instead of being caught
by the generic DataStoreError warn-only path.
Precedent for this class of fix: the --model silent no-op entry below (same
section) — an error the user sees beats a wrong artifact reported as success.
Related, but distinct. #282
fixed a separate defect in the same media-picker surface (--ref <uuid>
selection failing on any ref after the first once the virtualised
(react-virtuoso) grid needed to scroll to render it) — a picker-navigation
bug, not a data-integrity one. #174
(open, in the "Open" section above) is a Flow-side full-page media-library
UI rollout that breaks entity attach on affected accounts — a different
code path, but the same general theme that the media-picker surface needs
ongoing hardening.
Residual risk (post-fix, not fully closed). The two-pass baseline settle
narrows the window, it doesn't remove it: the agentic poll loop still stops
scraping the moment the new-UUID count on a single scrape equals the
requested count. A pre-existing tile that lazy-renders after the 0.5s
baseline settle but before the real generation actually completes
(generations take 30–60s) lands inside that window at exactly the expected
count — the ambiguity fail-fast in layer 2 only triggers on an excess of
new UUIDs, so it never sees this case. Layer 3's pre-download guard and
collision escalation then only catch it if the misattributed asset happens
to already be in local history for the profile — an asset created in the
Flow web UI, generated on another machine, or recorded under a different
profile DB has no local history row to collide with, so it can still slip
through all three layers: wrong file on disk, exit 0. Mitigations until this
is closed: run generations in a dedicated, low-asset project (fewer
pre-existing tiles means a smaller lazy-render population to collide with),
and visually verify anchor/canonical images before referencing them
downstream in i2i. The stable-break hardening shipped post-v0.32.0 (#283 /
PR #292): the loop now requires the SAME new-UUID set on two consecutive
scrapes at the expected count before trusting it, which narrows this window
to a pre-existing tile that lazy-renders AND holds stable across two 0.5s
scrapes at exactly the expected count (or first hits the count on the final
poll before the 180s deadline). Narrowed, not closed — the dedicated-project
mitigation still applies.
Known gap: the shell multi-prompt path records no history at all.
gflow run --config <file> and gflow image t2i with multiple prompts
(positional, --prompts-file, or --stdin) both go through
image_batch.run_image_batch / run_one_image_prompt, which never opens an
OperationRecorder or calls record_generated_images — no local history row
is written for these runs at all. Layer 1 (the agentic driver's two-pass
baseline settle + ambiguity fail-fast in await_images) still applies since
it lives in the transport, independent of recording. Layers 2 and 3
(verify_media_attribution's pre-download guard and the post-download
collision escalation) both depend on local history via
OperationRecorder.is_media_recorded() / record_generated_images(), so
neither guard exists on this path — this is a pre-existing gap, not a
regression from this fix. gflow image batch (the manifest path, via
run_manifest_image_batch) already threads a recorder through and is covered
by all three layers. Accepted low-risk gap (#283 closed with this noted as
un-shipped remainder; re-file if it bites in practice).
Workaround (pre-fix): none — on an affected version, manually verify the
downloaded file matches the prompt before trusting a t2i/i2i result.
Profile named default is opaque — no Google account identity¶
- Status: Resolved · Severity: Was-Low (UX confusion, no data loss) · Was-affecting: all versions through v0.9.x · Fixed in: v0.10.0 via PR #110 (2026-05-28) · Tracked: issue #92
The first-run default profile name default gave no indication of which Google
account it belonged to, what locale it used, or whether it was valid. On
developer machines with multiple Google Pro/Ultra accounts, this caused confusion
when test profiles, expired sessions, or stale gflow auth login runs silently
wrote to the wrong directory.
Resolution: gflow auth login now writes a .gflow_account file to the
profile directory immediately after the session is verified. profile_store.list_profiles()
surfaces this as ProfileMeta.google_account, and gflow auth list (both table
and --json) now includes a Google account column. The first-run default
profile is automatically renamed to the email local-part (e.g. profile_your-name)
once the email is known, and config.toml is updated atomically.
Profiles created before this fix continue to work and display unknown in the
account column. Re-running gflow auth login against an existing profile backfills
the .gflow_account file.
See AUTHENTICATION.md § Profile naming for the
new naming convention and AUTHENTICATION.md § gflow auth list
for the updated --json schema.
gflow image t2i/i2i --model was a silent no-op on ui_automation¶
- Status: Resolved · Severity: Was-Medium (wrong model = wrong cost + quality, silently) · Was-affecting: v0.7.0 through v0.8.1 · Fixed in: develop post-v0.8.1 via PR #48 (2026-05-24)
Pre-fix, gflow image t2i --model nano-banana-2 (or any other model) under
ui_automation would set the wire-level model field correctly but never
click the model picker in the editor UI, so Flow used whichever model the
UI's dropdown was last set to (typically the account default). Users got
their requested model silently substituted for the default — no error, just
wrong output and wrong credit cost. Fixed by _select_image_model in
src/gflow_cli/api/transports/ui_automation.py which mirrors the new
_select_video_model helper. If you ran gflow image t2i --model <X>
against v0.7.0–v0.8.1 and noticed the output didn't match <X>, this is
why; re-run on the next release (≥ v0.9.0).
aisandbox-pa generation 401 — bypassed by the ui_automation transport¶
- Status: Resolved · Severity: Was-High (blocked image gen via HTTP transports) · Fixed in: v0.7.0
The two long Open-section entries above (Image generation returns HTTP 401 and REST API 401 — all aisandbox-pa.googleapis.com generation endpoints blocked) were closed by the same architectural change: UiAutomationTransport drives the Flow web UI so Flow's own JavaScript issues every generation request with the full browser auth context (cookies, reCAPTCHA, Origin/Referer headers). The 401 had affected every direct HTTP call from evaluate_fetch / bearer / sapisidhash; those transports now live under src/gflow_cli/api/transports/experimental/ and are not on the production path.
End-to-end live-verified on the ffroliva profile across 9:16, 16:9, 1:1, and 4:3 aspect ratios; see docs/LIVE_VERIFICATION_v0.7.0.md for timing, file sizes, and exact filenames. Video T2V uses the same approach (Phase A — PR #23 — merged 2026-05-19).
G12 "browser not secure" block — Google rejects automated sign-in¶
- Status: Resolved · Severity: Critical (blocked
gflow auth login) · Fixed in: v0.6.0a2
Google's sign-in flow (accounts.google.com/v3/signin/rejected) detected Playwright's bundled Chromium as an automated browser and refused the login with no user-facing error.
Root cause (timing race): Without --disable-blink-features=AutomationControlled,
Blink's C++ engine sets navigator.webdriver = true as a non-configurable, non-writable
native property at Chrome startup — before any JavaScript (including add_init_script)
can run. The Object.defineProperty override silently fails. With the flag, the property
is never set; the JS override then works as belt-and-suspenders.
Resolution: v0.6.0a2 adds RealChromeStrategy — a new auth strategy that launches
the system's real Google Chrome via Playwright's channel="chrome" with stealth flags.
# Bypass G12 block explicitly:
gflow auth login --browser chrome
# Or rely on auto-detection (default behaviour; picks real Chrome if installed):
gflow auth login
A cosmetic "You are using an unsupported command-line flag" notice may appear briefly in the Chrome window — this is harmless and can be dismissed. It is the accepted trade-off for bypassing G12.
v0.1 — provider methods are stubs¶
- Status: Resolved · Severity: Critical (blocked usage) · Fixed in: v0.2.0a1
The v0.1 scaffold left upload_image, start_generation, get_job, download raising NotImplementedError. v0.2.0a1 wired the video routes (T2V/I2V/batch) on a new gflow_cli.api.client.FlowApiClient and removed the legacy providers/ + models modules. v0.3.0a1 added the image routes (gflow image upload/t2i/i2i) on the same client.
Reporting a new issue¶
If you hit something not listed here:
- Search existing issues at https://github.com/ffroliva/gflow-cli/issues.
- If none match, open a new issue with:
gflow-cliversion (gflow --version)- Python version (
python --version) - OS + version
- Exact command that failed + full error output
- What you expected vs. what happened
- For security issues, see docs/SECURITY.md § Reporting — email instead of public issue.