Skip to content

cli: --duration without --model on i2v dies as "Unexpected error" (exit 1) instead of the usage error that already exists #630

Closed

Description

@ffroliva

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 ValueError surfaces through the CLI as "Unexpected error." (exit 1) and the explanation is lost. Raising a UsageError here 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:
    return

For 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)

  1. Narrow: have the i2v call site pass its effective default (I2V_DEFAULT_MODEL.value when model is None) into the guard. Smallest diff; leaves t2v semantics untouched.
  2. Root cause: make the DTO raise a typed gflow error instead of a bare ValueError, so every caller — CLI, MCP, direct FlowApiClient — renders it as RFC 9457 with a remediation hint rather than "Unexpected error". This deletes the reason _reject_duration_without_control exists 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.

Refs #451, #288, #626.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions