unslothai/unsloth · error · HTTPException
Invalid block_id: only uid-namespaced blocks can be deleted
Error message
Invalid block_id: only uid-namespaced blocks can be deleted
What it means
HTTP 400 raised by DELETE /seed/unstructured-block/{block_id} when block_id passes the safe-id check but is not a 32-char lowercase hex uid (_UPLOAD_UID_RE, the UUID4-hex namespace the frontend generates). Legacy node-id directories (n1, n2, ...) can be shared by several recipes, so bulk deletion is deliberately restricted to uid-namespaced blocks, which have exactly one owner.
Source
Thrown at studio/backend/routes/data_recipe/seed.py:597
if not any(block_dir.iterdir()):
block_dir.rmdir()
except OSError:
pass
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():View on GitHub (pinned to 203007d190)
Solutions
- Use the uid-namespaced block_id (32-char hex) returned when the upload block was created.
- For legacy node-id blocks, delete files individually via DELETE /seed/unstructured-file/{block_id}/{file_id} instead of bulk-deleting the block.
- Migrate old recipes to uid-namespaced blocks so bulk cleanup works.
Example fix
# before
delete(f'/seed/unstructured-block/{node_id}') # node_id='n1' -> 400
# after
files = list_files(node_id)
for f in files:
delete(f'/seed/unstructured-file/{node_id}/{f.file_id}') # file-by-file for legacy blocks Defensive patterns
Strategy: type-guard
Validate before calling
const UID = /^[0-9a-f]{32}$/;
const canBulkDelete = UID.test(blockId);
if (!canBulkDelete) { for (const f of await listBlockFiles(blockId)) await deleteFile(blockId, f.file_id); }
else { await deleteBlock(blockId); } Type guard
const UPLOAD_UID_RE = /^[0-9a-f]{32}$/;
function isUidNamespacedBlock(id: string): boolean {
return UPLOAD_UID_RE.test(id);
} Try / catch
Catch the 400 and branch: if the id is legacy, fall back to per-file deletes; otherwise fix the caller to pass the uid block id.
Prevention
- Always create upload blocks with a fresh UUID4 hex namespace.
- Keep node id and upload uid as distinct fields in frontend state so they are never swapped.
When it happens
Trigger: Calling block delete with a legacy id like 'n1' or any non-UUID4-hex id; using a recipe node id instead of the upload namespace uid as block_id.
Common situations: Older recipes/frontend versions that stored uploads under node ids; new code path mixing up node id and upload uid; manual API calls constructed from node ids.
Related errors
- Unsupported file type: {ext}. Allowed: {allowed}
- {str(exc)}
- Dropped dataset could not be read.
- Dropped dataset is empty
- No dataset file was provided
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/69bd6d650d2e4507.
Report an issue: GitHub.