Repository navigation
Description
Symptom
gflow video i2v --initial-frame A --end-frame B "prompt" --duration 4 (no --model) exits 1 with:
Unexpected error. Re-run with GFLOW_CLI_DEBUG_TRACEBACK=1 to see the real
error. If this persists, file a bug at https://github.com/ffroliva/gflow-cli/issues.
With GFLOW_CLI_DEBUG_TRACEBACK=1 the real cause is visible and perfectly clear:
ValueError: model 'veo_3_1_lite' has no duration control in Flow's UI, so ...
The user is told to file a bug for a plain usage error whose explanation already exists.
Root cause
_reject_duration_without_control (src/gflow_cli/cli_video.py:166) exists precisely to stop this. Its own docstring says so:
The DTO guards this too (defence in depth for API callers), but a bare
ValueErrorsurfaces through the CLI as "Unexpected error." (exit 1) and the explanation is lost. Raising aUsageErrorhere gives exit 2 and prints the reason.
But it returns early when the user did not pass --model:
if duration is None or model is None:
returnFor video i2v an omitted --model is not "no model" — it defaults to I2V_DEFAULT_MODEL (veo_3_1_lite), which renders no duration control. So the guard is bypassed in exactly the default case, and the DTO's ValueError escapes as error_unhandled.
Called at cli_video.py:1151, :1329, :1501.
Impact
Every user who passes --duration to gflow video i2v without also passing --model — the most natural way to try the flag — gets "Unexpected error, file a bug" instead of the actionable message. Exit 1 instead of the intended exit 2.
t2v differs: with no --model the run inherits Flow's UI default, which gflow does not control, so the effective model genuinely is unknown there. Any fix must not assume otherwise.
Suggested fix (two options)
- Narrow: have the i2v call site pass its effective default (
I2V_DEFAULT_MODEL.valuewhenmodel is None) into the guard. Smallest diff; leavest2vsemantics untouched. - Root cause: make the DTO raise a typed gflow error instead of a bare
ValueError, so every caller — CLI, MCP, directFlowApiClient— renders it as RFC 9457 with a remediation hint rather than "Unexpected error". This deletes the reason_reject_duration_without_controlexists at all, which makes it the more YAGNI-consistent fix, but it changes an exception type other code may catch.
Option 2 is likely correct; it needs a check of who catches ValueError around request construction, and it affects the MCP surface too (an MCP agent currently gets the same opaque failure).
How it was found
While closing the --duration 10 verification gap from #626. The same investigation found that all three credit-bearing tests in tests/e2e/test_i2v_flags_e2e.py passed a bare --duration 4 with no --model — so every one of them would have failed since #451/#288 shipped. They never run in CI (opt-in, credit-bearing), so nothing noticed. Those are fixed in the same change as this report; this ticket is the product defect they were masking.
Related: #620 (an e2e test passing on an empty loop) is the same class — opt-in tests rotting unobserved.
Activity
chore(release): gate PROJECT_STATUS currency; fix rotted i2v e2e tests (
refactor: collapse the duplicated duration-guard branches (ponytail-r…
fix(cli,mcp): --duration with no --model no longer dies as "Unexpecte…