calesthio/OpenMontage · error · Error

fetch failed ${r.status}: ${url}

Error message

fetch failed ${r.status}: ${url}

What it means

Thrown when duration cannot be converted to an integer at all — int(value) raises TypeError (None, list, dict) or ValueError ("abc", "4s", ""). The tool wraps that failure in a consistent ValueError describing the accepted range. This is the type/format gate before any range checking happens.

Source

Thrown at ink-theater/mocap/add-motion.mjs:31

// only needs an alias added to bvh2clip.mjs's ALIAS table.
import { writeFileSync, readFileSync, readdirSync, existsSync, mkdirSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const here = dirname(fileURLToPath(import.meta.url));
const [, , name, source, category = "action", desc = ""] = process.argv;
if (!name || !source) { console.error("usage: node add-motion.mjs <name> <source> [category] [description]"); process.exit(1); }

async function resolveBvh(src) {
  const tmp = join(here, "src"); mkdirSync(tmp, { recursive: true });
  const out = join(tmp, name + ".bvh");
  if (existsSync(src)) { writeFileSync(out, readFileSync(src)); return out; }
  let url = src;
  const CMU = "https://raw.githubusercontent.com/una-dinosauria/cmu-mocap/master/data";
  if (/^\d{2,3}_\d+$/.test(src)) { const subj = src.split("_")[0].padStart(3, "0"); url = `${CMU}/${subj}/${src}.bvh`; }
  else if (/^\d+\/\d{2,3}_\d+$/.test(src)) { url = `${CMU}/${src}.bvh`; }
  const r = await fetch(url); if (!r.ok) throw new Error(`fetch failed ${r.status}: ${url}`);
  writeFileSync(out, Buffer.from(await r.arrayBuffer())); return out;
}

const bvh = await resolveBvh(source);
execFileSync("node", [join(here, "bvh2clip.mjs"), bvh, join(here, "clips", name + ".json"), "--fps", "30", "--max", "180", "--name", name], { stdio: "inherit" });

const clipsDir = join(here, "clips");
const clips = {};
for (const f of readdirSync(clipsDir).filter((f) => f.endsWith(".json"))) clips[f.replace(/\.json$/, "")] = JSON.parse(readFileSync(join(clipsDir, f), "utf8"));
writeFileSync(join(here, "clips.js"), "window.INK_CLIPS=" + JSON.stringify(clips) + ";");

const catPath = join(here, "catalog.json");
const cat = existsSync(catPath) ? JSON.parse(readFileSync(catPath, "utf8")) : [];
const entry = { name, category, desc: desc || name, frames: clips[name].frameCount, source };
const i = cat.findIndex((c) => c.name === name);
if (i >= 0) cat[i] = entry; else cat.push(entry);
cat.sort((a, b) => a.name.localeCompare(b.name));
writeFileSync(catPath, JSON.stringify(cat, null, 2));

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Pass duration as a plain integer (4-15), -1, or "auto".
  2. Strip units and whitespace from user-supplied strings before the call (e.g. parse "8s" to 8).
  3. Default null duration to "auto" instead of forwarding None.

Example fix

# before
inputs = {"duration": "8s"}
# after
import re
m = re.search(r"\d+", "8s")
inputs = {"duration": int(m.group()) if m else "auto"}
Defensive patterns

Strategy: validation

Validate before calling

def normalize_duration(v, default="auto"):
    if v is None:
        return default
    s = str(v).strip()
    if s == "auto":
        return "auto"
    digits = s.rstrip("s sec").strip()
    try:
        return int(digits)
    except ValueError:
        return default

Type guard

def is_parseable_duration(v) -> bool:
    if v is None or isinstance(v, bool):
        return False
    if isinstance(v, int):
        return True
    if isinstance(v, str):
        s = v.strip()
        return s == "auto" or (s.lstrip('-').isdigit() and len(s) < 8)
    return False

Try / catch

try:
    tool.run(inputs)
except ValueError as e:
    if "duration must be an integer" in str(e):
        inputs["duration"] = "auto"
        tool.run(inputs)
    else:
        raise

Prevention

When it happens

Trigger: duration=null/None, duration="10s", duration="", duration=[10], or any string that is not a clean integer passed to seedance_ark's duration parameter.

Common situations: LLM-generated arguments with units attached ("8s"), nulls from optional JSON fields forwarded unfiltered, duration copied from a UI text field containing whitespace or unit suffixes.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/8bacf18d51930e5e. Report an issue: GitHub.