calesthio/OpenMontage · error · HTTPException
path escapes project
Error message
path escapes project
What it means
The final range check in _normalize_duration: the value parsed to an integer, but that integer is not -1 and not within 4..max_seconds (default 15). Note a separate earlier check also rejects non-canonical forms like "4.5"; this specific message fires only for genuinely out-of-range integers such as 3, 16, or 30.
Source
Thrown at backlot/server.py:251
yield _sse({"type": "change", "project_id": changed})
finally:
hub.unsubscribe(q)
return StreamingResponse(stream(), media_type="text/event-stream", headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
})
# ---- Thumbnails (downscaled, cached on disk) ------------------------
@app.get("/thumb/{project_id}/{file_path:path}")
async def thumb(project_id: str, file_path: str, w: int = 640) -> FileResponse:
project_dir = _safe_project_dir(project_id)
target = (project_dir / file_path).resolve()
try:
target.relative_to(project_dir.resolve())
except ValueError:
raise HTTPException(status_code=403, detail="path escapes project")
if not target.is_file():
raise HTTPException(status_code=404, detail="media not found")
width = min(THUMB_WIDTHS, key=lambda x: abs(x - w))
cached = await asyncio.to_thread(_thumbnail_for, target, width)
if cached is None:
# Never fall back to raw video bytes for an <img> consumer (F-03);
# non-thumbable images are safe to serve as-is.
if target.suffix.lower() in {".mp4", ".webm", ".mov"}:
raise HTTPException(status_code=404, detail="no poster frame available")
return FileResponse(target)
return FileResponse(cached, media_type="image/jpeg")
# ---- Media (range requests handled by FileResponse) ---------------
@app.get("/media/{project_id}/{file_path:path}")
async def media(project_id: str, file_path: str) -> FileResponse:
project_dir = _safe_project_dir(project_id)
target = (project_dir / file_path).resolve()View on GitHub (pinned to 95e1c3d0ab)
Solutions
- Clamp the requested duration into [4, 15] or send -1 to let Ark pick.
- Check which model variant you target — max_seconds is a parameter (default 15); if the model supports longer clips, pass the correct max through the tool's configuration rather than guessing.
- Split a long clip request into multiple 15s generations and concatenate.
Example fix
# before
inputs = {"duration": 30}
# after
MAX = 15
inputs = {"duration": min(max(4, requested), MAX) if requested > 0 else -1} Defensive patterns
Strategy: validation
Validate before calling
def clamp_duration(requested, lo=4, hi=15):
if requested in ("auto", -1, None):
return -1
return max(lo, min(hi, int(requested))) Try / catch
try:
tool.run(inputs)
except ValueError as e:
if "duration must be between" in str(e):
inputs["duration"] = -1
tool.run(inputs)
else:
raise Prevention
- Clamp user/LLM-provided durations into 4-15 before calling.
- Remember -1 delegates the choice to the model — use it instead of guessing.
When it happens
Trigger: duration=3, duration=16, duration=0, or duration=-2 passed to seedance_ark. Also passing a longer duration (e.g. 20) intended for a different provider whose max is higher.
Common situations: Porting prompts/configs from another video model that allows 5-10s or up to 60s; assuming -1 means 'unlimited' and then also trying values like 60; off-by-one attempts at the boundary (duration=15 works, 16 does not).
Related errors
- ${res.status} ${url}
- fetch failed ${r.status}: ${url}
- Checkpoint failed schema validation: {exc.message}
- media not found
- no poster frame available
AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15).
Data as JSON: /api/errors/6ffa56bdeb77d55a.
Report an issue: GitHub.