affaan-m/ECC · error · RuntimeError

export did not create a timeline

Error message

{fmt} export did not create a timeline: {tp}

What it means

forge.py raises RuntimeError after writing each timeline export (fcpxml and edl) when the path returned by write_timeline does not exist on disk. It is a post-condition check that the exporter both returned a truthy path and actually created the file; if either silently failed the export is declared broken.

Solutions

  1. Check that the parent directory of out_path exists and is writable before running forge
  2. Verify disk space is available; a full disk makes file writes fail
  3. Inspect write_timeline in tl_mod to confirm it actually writes the file and raises on failure rather than returning a path unconditionally
  4. Run with the same out_path in a fresh directory to rule out stale permission or symlink issues

Example fix

// before
out_path = work_dir / "cut"
for fmt in ("fcpxml", "edl"):
    tp = tl_mod.write_timeline(...)
// after
out_path = work_dir / "cut"
out_path.parent.mkdir(parents=True, exist_ok=True)
for fmt in ("fcpxml", "edl"):
    tp = tl_mod.write_timeline(...)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
if not Path(out_path).parent.exists() or not Path(out_path).parent.stat().st_mode & 0o200:
    raise SystemExit(f"output directory not writable: {out_path.parent}")

Type guard

def is_writable_dir(p) -> bool:
    from pathlib import Path
    p = Path(p)
    return p.is_dir() and p.stat().st_mode & 0o200 != 0

Try / catch

try:
    forge(...)
except RuntimeError as e:
    print(f"timeline export failed: {e}"); check_disk_and_perms(out_path)

Prevention

When it happens

Trigger: write_timeline returns a path string that does not point to a created file - typically because the writer failed silently, the output directory was deleted mid-run, or the path is unwritable due to permissions.

Common situations: Running forge in a read-only work directory, a custom write_timeline override returning the intended path without writing, disk-full conditions where the open() for the export fails but the path is still returned, or out_path parent directories that do not exist.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/1f68125996e5ad41. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/forge.py:287

    # timeline that can be re-cut, re-ordered and re-graded rather than as a
    # single clip somebody has to razor by hand.
    #
    # Shot files are kept: the timeline references them by absolute path, so
    # deleting work_dir breaks the handoff even though the mp4 still plays.
    tl_clips = [
        {"path": str(Path(c).resolve()),
         "duration": frame_mod.probe(c).duration,
         "name": Path(c).stem}
        for c in order
    ]
    timelines = {}
    for fmt in ("fcpxml", "edl"):
        tp = tl_mod.write_timeline(
            tl_clips, fps=FPS, out_path=out_path.with_suffix("." + fmt),
            fmt=fmt, title=f"{genre}_cut", width=W, height=H,
        )
        if not Path(tp).is_file():
            raise RuntimeError(f"{fmt} export did not create a timeline: {tp}")
        timelines[fmt] = str(tp)
        print(f"  timeline -> {tp}")

    asm.write_manifest(out_path.with_suffix(".json"), {
        "genre": genre,
        "work_directory": str(work_dir),
        "created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "geometry": {"width": W, "height": H, "fps": FPS},
        "takes": [str(t) for t in takes],
        "base_video": base_video,
        "base_ratio": base_ratio if base_shots else 0.0,
        "overlays": ov,
        "shots": len(order),
        "generated_shots": len(gen_shots),
        "base_shots": len(base_shots),
        "duration": round(final.duration, 3),
        "duration_contract": duration_contract,
        "grade_strength": strength,

View on GitHub (pinned to 8321021c54)