Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gflow",
"version": "0.73.1",
"version": "0.73.2",
"description": "Reusable development and operations workflows for gflow-cli",
"author": {
"name": "Flavio Oliva",
Expand Down
55 changes: 54 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.73.2] — 2026-09-12

### Fixed

- **A missing browser-strategy marker no longer reports as a network problem (#796).**
The Playwright cookie reader refuses a profile whose `.gflow_browser_strategy` marker
is absent, and that `SecurityError` was flattened into `VERIFICATION_ERROR` — whose
guidance is "check network connectivity", for a file on the user's own disk. It is
also precisely the state a failed first login leaves behind, since that rolls the
marker back. A new `PROFILE_MARKER_MISSING` outcome carries its own message and the
remediation that actually works (`gflow auth login --browser chrome`), on the CLI and
on the MCP twin (HTTP 409, `retryable: false` — it is neither a network blip nor an
expired session).
- **`gflow credits` no longer blames SAPISID for a host it never contacted (#795).** When
labs.google answers 200 with no `access_token` — the normal shape for an account Google
has migrated to `flow.google.com` — the failure was raised as "aisandbox-pa
authentication failed" with the remediation "SAPISID cookie missing, expired, or
unreadable". aisandbox-pa had not been contacted, and SAPISID was present and fine, so
the advice sent users into a re-login loop that cannot terminate on that cohort. The
remediation now names the real cause. `credits` itself remains labs-only on migrated
accounts — tracked in #795.
- **Incident bundles from a `flow.google.com` failure are no longer blank (#792).** The
migrated composer parked its pooled page on `about:blank` in a bare `finally`, so on
the FAILURE path it navigated away *before* `FlowApiClient._capture_incident` read the
page — whose own contract is to stage the bundle "while the page is still alive".
Every migrated video and image failure therefore shipped `tag_counts.div = 0`, a white
screenshot and `host_category = "other"` beside a network journal that proved the app
was alive, which reads exactly like a lost browser tab and is unusable as evidence.
The park is deferred past the capture and drained at the **top of the next run**, before
the route decision reads `page.url` — which is where the invariant it protects (a stale
project URL must not route the next request) is actually consumed, and the only place
that also covers the `post_with_retry` path, where a retryable 5xx re-enters the
transport without passing the client's failure boundary at all. Cancellation still
attempts the park inline and latches, since no bundle is staged for it.
Note a failure bundle's `sensitive/screenshot.png` is now a real capture of the
logged-in page rather than a blank frame — the review-before-sharing posture in
SECURITY.md applies to it as it already did to every other screenshot.

- **The `/about` landing's `retryable=False` is now a measurement, not a preserved
default.** A live occurrence was caught on a second account and the #756 stability
probe re-run unmodified: **5/5** attempts landed on `/about` over ~3 minutes, on the
account's own project, with a healthy session — so a retry is doomed and costs ~35 s
each. Four places that said the measurement *could not* be made are corrected — two
code comments, the exit-31 row in [USAGE](docs/USAGE.md) and the `retryable` note in
[MCP](docs/MCP.md); behaviour is unchanged. Still unmeasured: the cause, and whether
it ever clears.
- **An auth-status test no longer depends on how wide the terminal is.** Several steps
assert a substring of Rich's output, which hard-wraps — so a temp path landing near
the wrap column split `experiments` into `profile_e` + `xperiments` and failed on
formatting rather than behaviour. Pinned at the shared `CliRunner` fixture, which is
the one chokepoint for every invoke in that file.

## [0.73.1] — 2026-09-11

### Fixed
Expand Down Expand Up @@ -4867,7 +4919,8 @@ shell-script template that branches on these codes.

First skeleton. Not functional end-to-end yet.

[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.73.1...HEAD
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.73.2...HEAD
[0.73.2]: https://github.com/ffroliva/gflow-cli/compare/v0.73.1...v0.73.2
[0.73.1]: https://github.com/ffroliva/gflow-cli/compare/v0.73.0...v0.73.1
[0.73.0]: https://github.com/ffroliva/gflow-cli/compare/v0.72.0...v0.73.0
[0.72.0]: https://github.com/ffroliva/gflow-cli/compare/v0.71.1...v0.72.0
Expand Down
35 changes: 27 additions & 8 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ is a per-account setting the labs.google app applies on every load (measured
once the account is flagged, re-running will not land the old frontend. Earlier text here said the rollout
"flaps" and told you to retry; that observation straddled the account's one-time
switch and is withdrawn. The REST surface (`gflow project list`, `gflow data …`)
is unaffected. Automated callers now receive `retryable: false` so retry loops
is unaffected; `gflow credits` is **not** — its token comes from `labs.google`, which mints none for a moved account ([#795](https://github.com/ffroliva/gflow-cli/issues/795), open; see the quota entry below). Automated callers now receive `retryable: false` so retry loops
stop instead of burning a doomed attempt each time.

**What gflow does today for the rest of the matrix:** recognises the migrated
Expand Down Expand Up @@ -220,11 +220,19 @@ at `z-index: 1000`, and Flow's composer is bottom-anchored in the same band. The
therefore lands **on** the settings trigger *and* on the image submit button.
Measured 2026-09-11 on the `ci-probe` profile: `elementFromPoint` over each returned
the bar's label span in 5/5 rendered samples, on the same profile and project where
the click had landed 3/3 the day before. **How widely it fires is unmeasured** — one
account observed blocked, one observed already-consented — so treat the recurrence
pattern as unknown rather than as once-per-profile. The labs driver survives it by
accident; `_bypass_onboarding` carries a text match on "Agree". The migrated driver
had no equivalent.
the click had landed 3/3 the day before.

**Who gets it:** anyone who has not yet dismissed it on that origin. The gate is a
single key, `localStorage["glue.CookieNotificationBar"]` on `flow.google.com` — not a
cookie, which is why no profile here carries `SOCS`. A clean browser profile reads the
bar visible 4/4, and removing that key on a consented profile brings it straight back
with both controls covered again. So it is the **default state**, and a stored
dismissal is what removes it: every new gflow profile meets it on its first Flow load,
and any profile whose stored dismissal Google resets meets it again — as `ci-probe` did
within seventeen hours.

The labs driver survives it by accident; `_bypass_onboarding` carries a text match on
"Agree". The migrated driver had no equivalent.

Through 0.73.0 the run fails at exit 23 with `it is covered by span` — the element on
top is the bar's label, whose identity lives in an `id` the occluder allowlist drops,
Expand Down Expand Up @@ -800,16 +808,27 @@ A block that survives dismissal now aborts pre-submit with exit 23 (probe `overl

---

### No in-CLI quota visibility — resolved
### No in-CLI quota visibility — resolved on labs, still open on the migrated host

- **Status:** Resolved 2026-09-05
- **Status:** Resolved 2026-09-05 for `labs.google` accounts · **Open** on accounts Google has migrated to `flow.google.com` · **Tracked:** [#795](https://github.com/ffroliva/gflow-cli/issues/795)

Use `gflow credits user` for the selected profile or `gflow credits list` for all saved
profiles. Both commands query Flow's current read-only credits endpoint with the saved browser
session; `--json` provides a stable automation contract. The equivalent MCP surface is
`gflow_get_credits`. The reported balance funds Veo video generation; image generation consumes
separate per-model daily quotas.

**On a migrated account there is still no in-CLI quota visibility.** The credits endpoint is
reached with a token minted by `labs.google`, and for an account Google has moved to
`flow.google.com` that session answers `200` with no `access_token` — it never mints one. So
`gflow credits user` / `list` and `gflow_get_credits` fail on that cohort with "the labs.google
session returned no access token". Through 0.73.1 this was reported as "aisandbox-pa
authentication failed … SAPISID cookie missing, expired, or unreadable", which sent migrated
users into a re-login loop that cannot terminate: aisandbox-pa had not been contacted and
SAPISID was present and fine. 0.73.2 fixes the *message* only — the remediation now names the
real cause. Reading a balance on the migrated host is **not** implemented
([#795](https://github.com/ffroliva/gflow-cli/issues/795), open). Generation is unaffected.

---

### `gflow serve` / MCP worker queue: interrupted post-submit tasks need manual reconciliation
Expand Down
17 changes: 9 additions & 8 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ When the project converges on the hexagonal target above, modules graduate to la
> **Note: this document describes the TARGET architecture, not the current
> package layout.** The current shape (per [PLAN.md § 2](../PLAN.md#2-architecture-steady-state)
> and [ADR #2](../PLAN.md#5-decision-log-adrs-in-miniature)) is the simpler
> `src/gflow_cli/{api/, auth/, data/, mcp/, services/, tools/, ui/, worker/}`
> packages plus flat modules — the `cli*.py` adapters, `browser_manager.py`,
> `chain*.py`, `composition.py`, `config.py`, `diagnostics.py`, `errors.py`,
> `image_batch.py`, `json_output.py`, `media.py`, `movie_manifest.py`,
> `observability.py`, `paths.py`, `profile_lease.py`, `profile_store.py`,
> `storage.py`, `update_check.py` (the once-a-day notice and `gflow update`'s
> `run_update`; its Click surface is `cli_update.py`), and `winsec.py` (inventory refreshed for
> v0.56.0, #507 — it had drifted a dozen modules behind).
> `src/gflow_cli/{api/, auth/, data/, flow_selectors/, mcp/, services/, tools/, ui/,
> worker/}` packages plus flat modules — the `cli*.py` adapters, `_cli_helpers.py`,
> `browser_manager.py`, `chain*.py`, `composition.py`, `config.py`, `diagnostics.py`,
> `errors.py`, `exceptions.py`, `file_integrity.py`, `image_batch.py`, `json_output.py`,
> `media.py`, `movie_manifest.py`, `observability.py`, `paths.py`, `profile_lease.py`,
> `profile_store.py`, `redaction.py`, `storage.py`, `update_check.py` (the once-a-day
> notice and `gflow update`'s `run_update`; its Click surface is `cli_update.py`), and
> `winsec.py` (inventory refreshed for v0.73.2 — it had drifted again since the v0.56.0
> refresh in #507; `AGENTS.md` carries the same list and is the one to diff against).
> The DDD layout below was deferred indefinitely; converge toward it incrementally
> if/when a second `Provider` justifies the split (`gflow serve` shipped as a
> thin adapter over the same core rather than forcing it).
Expand Down
2 changes: 1 addition & 1 deletion docs/AUTHENTICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ Probing Flow session (may take up to ~45s on a slow network)...
Flow session verified as you@example.com.
```

> Note: `cookies_present: True` only confirms the file exists. The final verdict line is the live probe result: a dead session prints a `gflow auth login` remediation hint and exits 1; a probe that cannot reach the endpoint (offline, 5xx) also exits 1 but suggests checking connectivity instead of re-logging in.
> Note: `cookies_present: True` only confirms the file exists. The final verdict line is the live probe result, and it has three failing shapes — all exit 1, each with a different hint. A **dead session** prints a `gflow auth login` remediation hint. A probe that **cannot reach the endpoint** (offline, 5xx) suggests checking connectivity instead of re-logging in. And a profile **missing its `.gflow_browser_strategy` marker** — the state a failed *first* login leaves behind, since that rolls the marker back — says so and points at `gflow auth login --browser chrome --profile <name>`; it is local profile state, not a network fault, and "check connectivity" would send you to the wrong place ([#796](https://github.com/ffroliva/gflow-cli/issues/796), 0.73.2). The MCP twin `gflow_auth_status` reports the same three as HTTP 401, 503 (`retryable: true`) and 409 (`retryable: false`) — see [MCP.md](MCP.md).

### `gflow auth list`

Expand Down
9 changes: 8 additions & 1 deletion docs/DEBUGGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,14 @@ bundle a user emails you:
It prints a scorecard (grade, per-question YES/NO/N/A, privacy verdict). The
live e2e benchmark (`tests/e2e/test_incident_quality_e2e.py`, credit-free)
enforces quality floors on real bundles so a regression that hollows out the
evidence fails CI even though the artifacts still exist.
evidence is caught even though the artifacts still exist.

**It is an opt-in gate, not a CI one** — it needs `-m e2e` and
`GFLOW_CLI_E2E_PROFILE`, so nothing runs it on a pull request. And until #792 it
graded only bundles captured deliberately on a *healthy* page, which is how a bug
that hollowed out every **failure** bundle on the migrated host survived it. The
file now also drives a real failed generation and asserts the DOM capture is not
blank; run it before shipping anything that touches capture or page lifecycle.

### Worker / daemon correlation

Expand Down
2 changes: 1 addition & 1 deletion docs/INDEX.md

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion docs/LIVE_VERIFICATION_v0.73.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,36 @@ without spending quota a second time.

---

## What was NOT verified, and why
## Addendum, same day — the two open items closed

Both were recorded below as *not verified* at tag time. Both were then measured, and this
section is the record rather than a rewrite of what shipped.

**Prevalence — answered by mechanism.** A count of users is not obtainable here (no
telemetry), but the gate is: `localStorage["glue.CookieNotificationBar"]` on the
`flow.google.com` origin. Not a cookie, which is why no profile carried `SOCS`. A clean
browser profile reads the bar visible **4/4**; a second untouched authenticated profile
(`pr389fresh2`) read **5/5**. Removing that one key on `ci-probe` — touching no cookie,
so no auth — flipped the editor from *both controls hit-testable* to **both covered by
`span#glue-cookie-notification-bar-1-label`**. So the bar is the origin's default state
and a stored dismissal is what removes it: every fresh profile meets it, and any profile
whose dismissal Google resets meets it again.

**The cure, against a live bar, on the released build.** With the bar restored by that
intervention, the ordinary CLI command ran on `cli_version: 0.73.1`:
`migrated.editor_ready` → **`migrated.cookie_bar_dismissed`** 86 ms later →
`image_settings_applied` → `prompt_typed` → `status: ok`, media `257d8f79-…`, a
651,576-byte `ffd8ffe0` JPEG that Pillow reads as 1024×1024 RGB.

Still not measured: the *covering* geometry on a second account's editor. `denon82` and
`pr389fresh2` both redirect to `/about`, so origin-level presence is measured on three
profiles and the occlusion on one.

---

## What was NOT verified at tag time, and why

*(The first two are closed by the addendum above; kept as written for the record.)*

**The cure against a live bar, outside the browser.** The consent state is not summonable
on demand, and this release's own control arm consumed it on the only profile that had it.
Expand Down
Loading
Loading