WhatBroke

My video agent pipeline produced a black or zero-byte file

AI video pipelines Last checked 2026-10-14

Almost always a failure that was never checked: a generation job that finished with an error status, a download that returned an error page, or an ffmpeg step whose exit code was ignored. Validate every artefact before the next stage consumes it.

What you saw

The pipeline reports success and the output file is 0 bytes, or a few hundred bytes of JSON or HTML with an .mp4 extension.

The video plays but is entirely black, or black with correct audio.

One shot in a ten-shot assembly is missing or frozen, and nothing in the logs says why.

It works when you run it by hand and fails on the schedule.

What it actually means

Video generation is asynchronous. You submit a job and poll an operation until it is done — and 'done' is not the same as 'succeeded'. A pipeline that only checks for completion will happily download the result of a failed job.

A zero-byte or tiny file is nearly always an unchecked HTTP response: an expired signed URL, a 401, a 429, or an error body written straight to disk because the writer never looked at the status code.

An all-black video is usually a real file produced by a real encode — a wrong pixel format, a filter chain that dropped the video stream, or a still-image input with no duration. The encode 'succeeded'; it just encoded nothing.

Works-by-hand-fails-on-schedule is the classic environment gap: different working directory, missing font or codec, no credentials in the scheduled environment, or a relative path that only resolves from your shell.

The underlying pattern is the same one that bites agent pipelines everywhere: a step that cannot prove it succeeded is treated as if it did, and the failure surfaces three stages later where it is expensive to diagnose.

The fix

  1. Check the operation's terminal state, not just that polling ended. Branch explicitly on error and stop the pipeline there rather than proceeding to download.

  2. Validate every downloaded artefact the moment it lands — size, type and decodability — before anything else touches it:

    set -euo pipefail
    test -s shot_01.mp4 || { echo 'empty file'; exit 1; }
    file shot_01.mp4 | grep -q 'ISO Media' || { echo 'not a video'; exit 1; }
    ffprobe -v error -select_streams v:0 \
      -show_entries stream=codec_name,width,height,nb_frames \
      -of default=noprint_wrappers=1 shot_01.mp4
  3. Detect black frames instead of trusting the encode. ffmpeg will tell you if the content is blank:

    ffmpeg -i shot_01.mp4 -vf blackdetect=d=0.5:pic_th=0.98 \
      -an -f null - 2>&1 | grep blackdetect
  4. Make every shell step fail loudly. set -euo pipefail at the top of any script, and check ffmpeg's exit code — it returns non-zero on failure and pipelines routinely discard it.

  5. Log the full response on failure, including status code and the first part of the body. The single most useful artefact when a nightly render breaks is the error page that got saved as an .mp4.

  6. Re-run failed shots in isolation rather than re-running the whole pipeline. Keep per-shot outputs addressable so one bad generation costs one generation.

How to stop it happening again

Adopt one rule: no stage consumes an artefact it has not validated. Validation is cheap, regeneration is not, and a 2KB HTML file masquerading as a video will propagate silently through every downstream step.

Use absolute paths and an explicit working directory everywhere. Most works-by-hand-fails-on-cron bugs die here.

Run the scheduled job in the same environment you test in, with the same credentials loaded the same way. Verify credentials at startup with a cheap read call rather than discovering they are missing at the download step.

Keep a manifest per render: every shot, its job id, its file size, its duration, its checksum. Assembly reads the manifest and refuses to run if anything is missing.

Alert on silence. A pipeline that produces nothing and says nothing is worse than one that crashes — add a final assertion that the output exists, is longer than a minimum duration, and is not black.

Cap spend and retries per run. Unchecked retries against a failing generation endpoint are how a broken pipeline turns into a bill.

Related questions

Why is the file a few hundred bytes instead of zero?

Because an error response was written to it. Open it in a text editor — it is usually JSON or HTML containing the real error, most often an expired URL or an auth failure.

The video is black but the audio is fine. What broke?

The video stream, not the job. Check your filter chain and pixel format, and confirm with ffprobe that a video stream exists and has frames. A filter that errors can leave you with audio only.

It works locally and fails on the schedule. Where do I look first?

Working directory, absolute paths, credentials and PATH — in that order. Print all four at the start of the scheduled run and the cause is usually visible immediately.

Other things that break

What Broke This Week

One email, most Thursdays. New symptoms, tool updates that quietly break things, and one fix worth stealing. Unsubscribe in one click.