unslothai/unsloth · error · HTTPException
Invalid block_id: outside upload root
Error message
Invalid block_id: outside upload root
What it means
HTTP 400 raised by DELETE /seed/unstructured-block/{block_id} as a defense-in-depth check: after resolving the path, block_dir must be relative to UNSTRUCTURED_UPLOAD_ROOT. With the prior regex guards this should never fire for well-formed ids; it exists to catch symlinked roots or path resolution surprises before shutil.rmtree runs.
Source
Thrown at studio/backend/routes/data_recipe/seed.py:601
return {"status": "ok"}
@router.delete("/seed/unstructured-block/{block_id}")
async def remove_unstructured_block(block_id: str):
"""Delete a block's upload directory; files on disk still count toward its quota.
Only uid-namespaced directories may be bulk-deleted: they have exactly one
owning block. Legacy node-id directories (n1, ...) can be shared by other
recipes, so they are managed file-by-file instead.
"""
_validate_safe_id(block_id, "block_id")
if not _UPLOAD_UID_RE.match(block_id):
raise HTTPException(400, "Invalid block_id: only uid-namespaced blocks can be deleted")
block_dir = (UNSTRUCTURED_UPLOAD_ROOT / block_id).resolve()
if not block_dir.is_relative_to(UNSTRUCTURED_UPLOAD_ROOT.resolve()):
raise HTTPException(400, "Invalid block_id: outside upload root")
if not block_dir.exists():
return {"status": "ok", "deleted": False}
try:
shutil.rmtree(block_dir)
except OSError as exc:
raise log_and_http_error(
exc,
500,
"failed to delete uploaded files",
event = "data_recipe.seed.unstructured_block_delete_failed",
log = logger,
) from exc
if block_dir.exists():
raise HTTPException(500, "failed to delete uploaded files")
return {"status": "ok", "deleted": True}
View on GitHub (pinned to 203007d190)
Solutions
- Check the configured uploads root env/config: make sure it points to the real directory, not a symlink chain that resolves inconsistently.
- Reproduce locally: print (UNSTRUCTURED_UPLOAD_ROOT / block_id).resolve() vs UNSTRUCTURED_UPLOAD_ROOT.resolve() to see the divergence.
- Normalize the root path at startup (resolve it once) so both sides of is_relative_to agree.
Defensive patterns
Strategy: validation
Validate before calling
// Ops-level: verify the configured root resolves consistently import os, pathlib root = pathlib.Path(os.environ['UNSTRUCTURED_UPLOAD_ROOT']).resolve() assert (root / 'probe').resolve().is_relative_to(root), 'root resolves inconsistently (symlink?)'
Try / catch
On 400 'outside upload root', stop and inspect deployment config — this signals an environment/symlink problem, not a bad request; retrying cannot fix it.
Prevention
- Point the uploads root at a real directory, not a symlink chain.
- Resolve the root once at startup and use the resolved path everywhere.
- Pin identical upload-root config across replicas.
When it happens
Trigger: UNSTRUCTURED_UPLOAD_ROOT itself resolves somewhere unexpected (e.g. symlinked data dir, container volume mount oddities), making the resolved block path fall outside the resolved root; otherwise effectively unreachable because _UPLOAD_UID_RE blocks traversal characters.
Common situations: Deployment where the uploads root is a symlink (resolve() follows it) while the joined path resolves differently; misconfigured uploads root env var pointing at a different filesystem view; exotic mount setups in containers.
Related errors
- Path is not in the browseable allowlist. Register it via POS
- Model path is outside Unsloth storage
- Dataset '{cleaned}' must not be a symbolic link.
- Dataset '{cleaned}' escapes the Studio datasets directory.
- GGUF conversion produced a symlink, refusing to relocate it:
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/31c22b75e8a451fd.
Report an issue: GitHub.