Skip to content

fix(migrated): report a refusal flow.google.com states on the wire (#906, #873) - #909

Merged
ffroliva merged 2 commits into
developfrom
bugfix/migrated-refusal-envelope
Sep 27, 2026
Merged

ffroliva merged 2 commits into
developfrom
bugfix/migrated-refusal-envelope

Conversation

@ffroliva

@ffroliva ffroliva commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

flow.google.com answers a refused submit with HTTP 200 and a batchexecute error envelope carrying Google's reason:

["wrb.fr","ogiZ0b",null,null,null,[7,null,[["type.googleapis.com/google.rpc.ErrorInfo",["PUBLIC_ERROR_UNUSUAL_ACTIVITY"]]]],"generic"]

batchexecute.parse_frames kept only frames with a string payload, so this frame was dropped. That is the root cause behind what #873, #906 and #907 reported: the video path waited out a retryable 60 s TransportTimeoutError (exit 9), and the image path raised WireFormatError("no ogiZ0b frame") (exit 7), while Flow's grid showed "We noticed some unusual activity".

  • batchexecute.rpc_errors() returns the envelopes parse_frames skips, via a shared row iterator. parse_frames' return type is unchanged for its five callers.
  • migrated_composer._submit_refusal() runs in both submit observers and maps a reason to a class:

Why the wire and not the grid card. A submit aborted in the browser renders the identical <flow-error-tile>: same ligatures, same structure, different copy. Only the reply tells a refusal from an abort. This was the blocker on #873 and #907.

Supersedes #873 and the refusal-card half of #907. Diagnosis by @stgmt, credited in the CHANGELOG.

Refs #906, #873, #907

Evidence

  • Spike: docs/superpowers/spikes/2026-09-27-migrated-refusal-is-on-the-wire.md, readings pre-registered. A submit with its reCAPTCHA token corrupted in flight was refused with the envelope above; an aborted submit gave requestfailed and no reply.
  • Memory note migrated-refusal-is-a-dom-card-not-a-wire-record corrected in place (the slug is cited by the council table).

Verification status

  • Offline BDD tests/features/migrated_refusal_envelope.feature, 4 scenarios. Red first: 3 failed for the right reason (the exact WireFormatError and timeout seen live). The status-5 guard passed before and after.
  • Parser unit tests in tests/api/transports/test_batchexecute.py
  • E2E, live: tests/e2e/test_migrated_refusal_live_bdd.py (-m e2e_image, opt-in GFLOW_CLI_E2E_RUN_REFUSAL=1) passed on a probe profile in 14 s. Flow refused, and gflow raised WafRejectionError naming the reason.
  • tests/api under -n 8: 1816 passed (develop: 1812 + 4 new)
  • ruff, ruff format, pyright src, repo hygiene, doc links, website PII, website mirror, council memory
  • Video refusal envelope observed live. Blocker: provoking it on our side risks one Veo charge and needs maintainer consent. Instead we asked the Migrated host: Flow refuses Playwright submits with 'unusual activity' card — manual Chrome on the same profile works #906 reporter, whose account is refused on every video submit, to run this branch or share one redacted YhhmEf/eb1hJf reply (see Migrated host: Flow refuses Playwright submits with 'unusual activity' card — manual Chrome on the same profile works #906). The video observer shares the framing and is covered offline, and a mutation check showed its call site is load-bearing.
  • SonarCloud

MCP / surface parity

No CLI option changed. The typed error comes from the shared migrated transport, so CLI, MCP and worker all receive the same WafRejectionError / ContentPolicyError envelope. docs/ACCOUNT_SAFETY.md already told users to expect WafRejectionError + PUBLIC_ERROR_UNUSUAL_ACTIVITY; that is now true on flow.google.com too.

, #873)

flow.google.com answers a refused submit with HTTP 200 and a batchexecute
error envelope: a null payload, the gRPC status in slot 5 and Google's
ErrorInfo reason inside it. parse_frames kept only frames with a string
payload, so every caller saw an empty reply: the video path waited out a
retryable 60 s TransportTimeoutError and the image path raised
WireFormatError("no ogiZ0b frame"), while Flow's grid showed "We noticed
some unusual activity".

batchexecute.rpc_errors now returns the envelopes parse_frames skips, and
both submit observers map a REASON to a class: PUBLIC_ERROR_UNUSUAL_ACTIVITY
to WafRejectionError (the labs path's class for the same reason, retryable
flag preserved) and the content-safety reasons to ContentPolicyError. A bare
status with no reason is not a refusal - the #723 queued entity submit
replies [5] - so it keeps today's behaviour.

Measured, not inferred: a submit with its reCAPTCHA token corrupted in
flight is refused with that envelope; an aborted submit gets no reply but
renders the identical <flow-error-tile>, which is why the grid card cannot
be the signal. New e2e (opt-in GFLOW_CLI_E2E_RUN_REFUSAL=1) provokes a real
refusal and passed on a probe profile.

Diagnosed by @stgmt in #873 and #906. Supersedes #873 and the
refusal-card half of #907.
@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4021e580-9515-4be0-9903-e1fb89c5896d

📥 Commits

Reviewing files that changed from the base of the PR and between 88ff537 and 9de3a13.

📒 Files selected for processing (21)
  • AGENTS.md
  • CHANGELOG.md
  • KNOWN_ISSUES.md
  • docs/DATA_LAYER.md
  • docs/DEBUGGING.md
  • docs/E2E_TESTING.md
  • docs/superpowers/memory/content-policy-text-scan-false-positives-on-page-chrome.md
  • docs/superpowers/memory/migrated-refusal-is-a-dom-card-not-a-wire-record.md
  • docs/superpowers/spikes/2026-09-27-migrated-refusal-is-on-the-wire.md
  • scripts/dev/spike_migrated_refusal_envelope.py
  • src/gflow_cli/api/transports/batchexecute.py
  • src/gflow_cli/api/transports/migrated_composer.py
  • tests/api/transports/test_batchexecute.py
  • tests/e2e/test_migrated_refusal_live_bdd.py
  • tests/features/migrated_refusal_envelope.feature
  • tests/features/migrated_refusal_live.feature
  • tests/features/test_migrated_refusal_envelope_steps.py
  • website/docs/DATA_LAYER.md
  • website/docs/DEBUGGING.md
  • website/docs/E2E_TESTING.md
  • website/docs/KNOWN_ISSUES.md
 ______________________________________________________________________________________________________________________________________________________________________________________________________
< Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. - Brian Kernighan >
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ffroliva added a commit that referenced this pull request Sep 27, 2026
#909 measured that the refusal is on the wire and rewrites this note in
full, including the stealth finding added here. Dropping the append
avoids a merge conflict between the two PRs.
- log migrated.submit_refused with rpc, code and reasons before raising
- state that retryability is the class default, as on labs (no override)
- feed the image scenario the 2026-09-27 capture byte for byte
- docs: E2E_TESTING opt-in row, DEBUGGING exit-10 row aligned with
  ACCOUNT_SAFETY, DATA_LAYER waf-rejection wording, KNOWN_ISSUES date
- memory: content-policy note points at the wire on flow.google.com;
  refusal note no longer links a spike that only exists on #908
- CHANGELOG: say the video refusal is covered offline, not observed live
@ffroliva
ffroliva marked this pull request as ready for review September 27, 2026 20:53
@ffroliva
ffroliva merged commit e757748 into develop Sep 27, 2026
19 checks passed
@ffroliva
ffroliva deleted the bugfix/migrated-refusal-envelope branch September 27, 2026 21:08
stgmt added a commit to stgmt/gflow-cli that referenced this pull request Sep 28, 2026
…com WAF fingerprinting

On the migrated host (flow.google.com), patchright's bundled browser carries
automation fingerprints that Flow's WAF keys on, so automated submits get
PUBLIC_ERROR_UNUSUAL_ACTIVITY even with valid cookies.

- GFLOW_CLI_CDP_LAUNCH=1: spawn the installed chrome.exe with a free
  --remote-debugging-port and attach patchright over CDP instead of
  launch_persistent_context. Requires GFLOW_CLI_BROWSER_ENGINE=patchright;
  on any other engine it raises ConfigurationError rather than silently
  starting the wrong browser.
- api/client.py: route _launch_persistent_context through cdp_launch when
  the env flag is set; drop --disable-blink-features=AutomationControlled
  from the stock args (it is itself a fingerprint).
- transports/ui_automation.py + experimental/evaluate_fetch.py: same flag
  removal plus ignore_default_args for --enable-automation/--no-sandbox.
- migrated_composer.py: replace filter(has=mat-icon-ligature) with XPath
  descendant selectors — has= runs a page-side evaluate that resolves to
  zero elements under patchright. Add a scoped-radios retry loop to
  _open_pane for Angular remounts, model the blocking-overlays XPath, and
  add GFLOW_HUMAN/GFLOW_HUMAN_TYPE humanized prompt typing plus humanized
  video/image submit clicks.
- Keep upstream ffroliva#909's WafRejectionError wire-read refusal detection; the
  refusal-card scrape half of this branch is superseded and removed.
- tests: extend the FakePage/FakeLocator test double for the new XPath
  selectors, wait_for_function, and mouse so the migrated suite still
  exercises the real driver paths.

Signed-off-by: stgmt <stgmt@users.noreply.github.com>
stgmt added a commit to stgmt/gflow-cli that referenced this pull request Sep 28, 2026
…com WAF fingerprinting

On the migrated host (flow.google.com), patchright's bundled browser carries
automation fingerprints that Flow's WAF keys on, so automated submits get
PUBLIC_ERROR_UNUSUAL_ACTIVITY even with valid cookies.

- GFLOW_CLI_CDP_LAUNCH=1: spawn the installed chrome.exe with a free
  --remote-debugging-port and attach patchright over CDP instead of
  launch_persistent_context. Requires GFLOW_CLI_BROWSER_ENGINE=patchright;
  on any other engine it raises ConfigurationError rather than silently
  starting the wrong browser.
- api/client.py: route _launch_persistent_context through cdp_launch when
  the env flag is set; drop --disable-blink-features=AutomationControlled
  from the stock args (it is itself a fingerprint).
- transports/ui_automation.py + experimental/evaluate_fetch.py: same flag
  removal plus ignore_default_args for --enable-automation/--no-sandbox.
- migrated_composer.py: replace filter(has=mat-icon-ligature) with XPath
  descendant selectors — has= runs a page-side evaluate that resolves to
  zero elements under patchright. Add a scoped-radios retry loop to
  _open_pane for Angular remounts, model the blocking-overlays XPath, and
  add GFLOW_HUMAN/GFLOW_HUMAN_TYPE humanized prompt typing plus humanized
  video/image submit clicks.
- Keep upstream ffroliva#909's WafRejectionError wire-read refusal detection; the
  refusal-card scrape half of this branch is superseded and removed.
- tests: extend the FakePage/FakeLocator test double for the new XPath
  selectors, wait_for_function, and mouse so the migrated suite still
  exercises the real driver paths.

Signed-off-by: stgmt <stgmt@users.noreply.github.com>
@ffroliva ffroliva mentioned this pull request Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant