Repository navigation
Description
What happens
gflow data download <image_media_id> accepts the id, opens a browser, waits 45 s, and then fails with exit 7 and a message that guesses two causes — both wrong here:
migrated host: no signed media URL for
4814faa7-…within 45s of opening its clip route. The media id may belong to another project, or the clip may have been moved to trash in Flow.-> Check request payload parameters or retry with a simpler prompt text. File a bug …
The image was generated on that same account twelve minutes earlier, is in that project, is not trashed, and its catalog row is intact (gflow data media shows it with kind: image and a local_path). None of the three suggestions applies.
Why it fails
migrated_recover.py:_await_signed_record opens /project/<pid>/edit/<media_id> and waits for the as29s batchexecute reply that carries a freshly signed flow-content.google/video/<workflow_id> URL. That is a video mechanism. An image's route does not emit that record, so found stays empty until the deadline.
There is no kind check anywhere on the path — services/media_recovery.py reads the catalog row and already has m.kind (it uses it at :69 only to build a disambiguation label), so the information needed to refuse fast is in hand and unused.
Related and already measured: docs/superpowers/spikes/2026-09-17-stranded-clip-recovery.md records that the listing's lh3 poster token answers =m18/=m22 with valid but transcoded MP4s — i.e. images and posters are served through a different CDN path than the signed video URL. 25 suffixes were measured and none returned an original. So this is a genuine mechanism difference, not a timing flake.
Reproduction
gflow image t2i "a single matte grey ceramic sphere on white seamless" --profile <p> --project <id> --json
# note images[0].media_name, delete the written file
gflow data download <that media_name> --profile <p>
Observed 2026-09-18 on a profile Flow serves from flow.google.com. migrated.recover_navigate fires; no as29s record for the media id follows; exit 7 at 45 s.
Impact
The command is described in terms that read as universal — "Fetch an already-generated asset from Flow by its media ID", and the CHANGELOG entry says "recover a billed asset whose download failed". The body text then switches to "the clip" and gflow data list videos, so the video-only scope is implied but never stated. A user with a stranded image follows the documented recovery path, waits 45 s, and is told to check a payload and simplify a prompt for a command that has neither.
Worth noting: this was explicitly left open at #865 as "images unproven". It is now proven — it does not work.
Suggested fix
Two independent pieces; the first is cheap and worth doing regardless of the second.
- Refuse fast and truthfully.
services/media_recovery.pyalready has the catalog row. Ifkindis not video, raise immediately with a message that says recovery is video-only today and links this issue — instead of a 45 s wait and three wrong guesses. Also drop the inheritedWireFormatErrorremediation on this path; "retry with a simpler prompt text" is the same defect as A retired labs tRPC route (404 "Flow RPCs have been deprecated") tells the user to simplify a prompt that does not exist #875 and is nonsense for a download command. - Support images — needs a spike. The signed-URL mechanism for images is not known; the poster-token suffixes measured in the 2026-09-17 spike all return transcodes, so the original's URL is somewhere else or nowhere.
Not covered here
Only one image, one account, one project. Whether a stranded image (one whose original download failed, rather than one I deleted locally) behaves differently was not tested — but the failure is in obtaining the URL at all, which is upstream of how the local file got lost, so it is unlikely to differ.
Activity
fix(data): refuse an image media id instead of blaming the project, t…
fix(capabilities): no-spend, video-only recovery, host-dependent uplo…