Skip to content

fix(chain): apply --out-file to video chain clips (#1293) - #1297

Merged
ffroliva merged 11 commits into
developfrom
bugfix/1293-chain-out-file
Nov 14, 2026
Merged

ffroliva merged 11 commits into
developfrom
bugfix/1293-chain-out-file

Conversation

@ffroliva

@ffroliva ffroliva commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Closes #1293. Closes #1300.

What happens today

gflow video chain ... --out-file clip.mp4 (or the deprecated -o) exits 0 and writes nothing at that path. Since v0.99.0 it prints a stderr notice; before that the option was a silent no-op.

The fix

Clip i, counted in manifest order from 1, is written to <stem>_<i><suffix> next to PATH (out/clip.mp4 gives out/clip_1.mp4, out/clip_2.mp4). This is the naming rule the MCP worker already uses for several outputs. run_chain takes an optional relocate hook that moves each downloaded clip before anything reads its path. So the chain table, the videos catalog, the next link's seed frame, the partial-failure paths and --json all report the path actually written.

Tests (each written first and seen failing)

  • test_chain_out_file_writes_each_link_to_a_numbered_path[--out-file|-o]
  • test_chain_out_file_resume_keeps_each_links_number
  • test_chain_out_file_move_failure_keeps_the_billed_clip_recorded

Full offline suite: 6892 passed, 34 skipped. All gates are green.

E2E

Pending: tests/e2e/test_chain_e2e.py with --out-file. It is paid (two i2v links) and will be run once on the final tree after review.

Review (code-review xhigh + ponytail-review xhigh), fixed in one round

Both reviewers ranked one pre-existing bug first, #1300, reproduced on develop with a real recorder. --resume-from numbered the remaining links from 0. That overwrote the chain row of an already-paid link, sent the first resumed link as an unseeded t2v, and billed a link twice on a later resume. With --out-file it would also have overwritten a paid clip on disk. It is fixed at the root here, because the new file naming depends on the same index:

  • run_chain takes a start index and the last completed clip. The request index, the recorded link_index, --json links[].index and the file number now all count from the manifest. The first resumed link goes out as i2v, seeded from the last clip. If that clip is gone, the run is refused before submit (exit 20, naming the file).
  • An existing clip_N file or folder is never overwritten. The clip stays where it was downloaded, recorded there, with a warning that names the link, the media id and the path.
  • The move is guarded and runs off the event loop. A raising hook leaves the clip recorded, and a partial copy is removed.
  • An --out-file under an existing file is a usage error before anything runs, including under --dry-run.
  • One naming helper (paths.numbered_path) is shared with the image command and the worker, and the relocator is typed.

Each fix has a test that was seen failing first. The #1300 test uses a real ChainLinkRecorder on a temporary database: 3 links, a failure, then two resumes. The offline suite passed: 6900 passed, 34 skipped.

E2E

tests/e2e/test_chain_e2e.py::test_chain_cli_out_file_writes_numbered_mp4s_and_seeds_link_2 drives the CLI (video chain <2-link manifest> --out-file <tmp>/clip.mp4 --yes --json). The result is recorded below.

E2E result (profile served flow.google.com, veo-lite, 20 credits, 636 → 616): test_chain_cli_out_file_writes_numbered_mp4s_and_seeds_link_2 passed. clip_1.mp4 and clip_2.mp4 were written as mp4s, --json reported exactly those paths, and link 2 was recorded as i2v.

After the e2e, develop (#1185) was merged in. cli_video.py then went past its size baseline, so chain_clip_relocator moved, verbatim, into paths.py next to the helpers it uses, with no logic change. The full offline suite passed afterwards: 6968 passed, 34 skipped. CodeRabbit was rate-limited on this PR and posted no review.

video chain accepted --out-file / -o PATH and never read it. run_chain
gains an optional relocate hook that moves each downloaded clip before
the chain table, the videos catalog, the seed extraction and --json see
its path. The CLI numbers links over the whole manifest, so a
--resume-from run writes the remaining links under the names a full run
would. The stderr notice is removed.
…move it (#1293)

A move failure (locked target, full disk) escaped run_chain as an
unexpected error before the link reached the chain table, so
--resume-from would bill it again. The clip now stays where it
downloaded, is recorded at that path, and a warning names the target.
Documents --out-file in the CONFIGURATION chain flag table.
@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 27 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c17c3f84-c3a1-4891-a013-ac3a65f9844b
Commits

Reviewing files that changed from the base of the PR and between 8553911 and 0a6a1e2.

Files selected for processing: 21

  • CHANGELOG.md
  • docs/CONFIGURATION.md
  • docs/PROJECT_STATUS.md
  • docs/USAGE.md
  • plugins/gflow/skills/gflow-cli/SKILL.md
  • skills/gflow-cli/SKILL.md
  • src/gflow_cli/chain.py
  • src/gflow_cli/cli_image.py
  • src/gflow_cli/cli_video.py
  • src/gflow_cli/paths.py
  • src/gflow_cli/worker/daemon.py
  • tests/cli/test_cli_video.py
  • tests/cli/test_cli_video_chain.py
  • tests/cli/test_cli_video_chain_out_file.py
  • tests/cli/test_out_file_deprecation.py
  • tests/cli/test_predictable_output.py
  • tests/e2e/test_chain_e2e.py
  • tests/features/test_video_chain_steps.py
  • tests/test_chain.py
  • website/docs/CONFIGURATION.md
  • website/docs/USAGE.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

…review)

The <stem>_<n><suffix> rule lived in four copies (video, image, worker,
chain). paths.numbered_path now holds it, next to relocate_video and
relocate_video_output, moved from cli_video. The chain relocator is
typed as run_chain declares it, and the chain --out-file test reuses the
shared CliRunner helper.
…ed i2v (#1300)

--resume-from passed links[skipped:] and run_chain numbered them from 0.
The first resumed link then overwrote link 0's chain row (keyed on
link_index), went out as an unseeded t2v, and left the row count short,
so a later resume billed a finished link again.

run_chain takes start_index and seed_clip: request mode, the result and
recorded index, --json links[].index and the --out-file number count
from the manifest, and the first resumed link is an i2v seeded from the
last completed clip. A resume whose clip is gone is refused before the
cost prompt with exit 20 naming the file, never sent as t2v.
#1293 review)

A second run with the same --out-file moved its clips over the first
run's. An existing <stem>_<i><suffix> now keeps the new clip at its
download path, recorded there.
 review)

Any exception from the relocate hook escaped run_chain before the link
was recorded, leaving a paid clip without a chain row. The move now runs
off the event loop, a failure keeps the clip at its download path,
recorded there, with a warning naming the link number, media id and
where the clip stayed, and a partial copy the move left is removed.
…anything runs (#1293 review)

The clips' folder was first created after the cost prompt and the
browser boot, so an --out-file whose parent is a file died as exit 1
with Chrome already up. It is now a usage error (exit 2) up front, in
--dry-run too.
…esume (#1293 review)

The --out-dir help and CONFIGURATION row still said the link mp4s land
there, USAGE's single-file note read as covering chain, and
PROJECT_STATUS still said the file was not written. Adds the #1300 fix to
the CHANGELOG entry.
The chain e2e calls run_chain directly, so it cannot see --out-file. The
new e2e_video test drives video chain over a 2-link manifest and checks
clip_1.mp4 and clip_2.mp4 are mp4s, --json reports exactly those paths,
and link 2 was recorded as i2v. Collected, not run here: it spends Veo
credits.
…xit 20 on resume (#1300)

Chain links record command 'video t2v'/'video i2v', so the e2e's filter
on 'video chain' matched nothing and would have failed after billing
(checked at $0 against a real catalog). The database holds that run
only, so the filter is dropped. USAGE now names the refused resume under
exit 20 and on --resume-from.
…out-file

# Conflicts:
#	CHANGELOG.md
#	src/gflow_cli/cli_video.py
#	src/gflow_cli/paths.py
@ffroliva
ffroliva merged commit bc4cad4 into develop Nov 14, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant