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

  1. Check the configured uploads root env/config: make sure it points to the real directory, not a symlink chain that resolves inconsistently.
  2. Reproduce locally: print (UNSTRUCTURED_UPLOAD_ROOT / block_id).resolve() vs UNSTRUCTURED_UPLOAD_ROOT.resolve() to see the divergence.
  3. 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

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


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/31c22b75e8a451fd. Report an issue: GitHub.