jamiepine/voicebox · warning · HTTPException

Source and destination are the same directory

Error message

Source and destination are the same directory

What it means

400 from POST /models/migrate. After resolving both paths, the route compares source.resolve() == destination.resolve(); if they refer to the same directory (after symlink resolution) it refuses with HTTPException(400, 'Source and destination are the same directory'). This is a guard against a no-op/in-place move that could destroy the cache.

Source

Thrown at backend/routes/models.py:133

    """Get the path to the HuggingFace model cache directory."""
    from huggingface_hub import constants as hf_constants

    return {"path": str(Path(hf_constants.HF_HUB_CACHE))}


@router.post("/models/migrate")
async def migrate_models(request: models.ModelMigrateRequest):
    """Move all downloaded models to a new directory with byte-level progress via SSE."""
    from huggingface_hub import constants as hf_constants

    source = Path(hf_constants.HF_HUB_CACHE)
    destination = Path(request.destination)

    if not source.exists():
        raise HTTPException(status_code=404, detail="Current model cache directory not found")

    if source.resolve() == destination.resolve():
        raise HTTPException(status_code=400, detail="Source and destination are the same directory")

    if destination.resolve().is_relative_to(source.resolve()):
        raise HTTPException(status_code=400, detail="Destination cannot be inside the current cache directory")

    progress_manager = get_progress_manager()
    model_dirs = [d for d in source.iterdir() if d.name.startswith("models--") and d.is_dir()]
    if not model_dirs:
        progress_manager.update_progress("migration", 1, 1, status="complete")
        progress_manager.mark_complete("migration")
        return {"moved": 0, "errors": [], "source": str(source), "destination": str(destination)}

    destination.mkdir(parents=True, exist_ok=True)

    same_fs = False
    try:
        same_fs = source.stat().st_dev == destination.stat().st_dev
    except OSError:
        pass

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Choose a destination on a different mount/volume (the whole point of migrate is usually to move across filesystems).
  2. GET /models/cache-dir to see the current source, then pick a destination whose realpath differs.
  3. If you intended to reorganize in place, that's not what migrate does — use filesystem tools directly.
  4. Resolve symlinks in the destination before sending (readlink -f) to confirm it differs from source.

Example fix

# before
curl -X POST http://localhost:8000/models/migrate -d '{"destination":"/Users/me/.cache/huggingface"}'
# (same as source)
# after
curl -X POST http://localhost:8000/models/migrate -d '{"destination":"/Volumes/External/models"}'
Defensive patterns

Strategy: validation

Validate before calling

import { resolve } from 'path';
async function migrateTo(dest: string) {
  const { path: cacheDir } = await (await fetch('/models/cache-dir')).json();
  if (resolve(dest) === resolve(cacheDir)) {
    throw new Error('Destination equals the current cache directory');
  }
  return await fetch('/models/migrate', {method:'POST', body: JSON.stringify({destination: dest})});
}

Try / catch

try {
  await fetch('/models/migrate', {method:'POST', body: JSON.stringify({destination})});
} catch (e) {
  if (e.response?.status === 400 && /same directory/i.test(e.response.detail)) {
    // prompt user for a different destination
  } else throw e;
}

Prevention

When it happens

Trigger: POST /models/migrate body {"destination": "/exact/same/path/as/cache"}; destination is a symlink that resolves to the cache dir; destination is '.' or the literal HF_HUB_CACHE path; relative destination that resolves under the current working directory equal to source.

Common situations: User pasted the current cache path into the destination field by mistake; UI pre-filled destination with the source; symlink aliasing the cache; running migrate twice with the second call pointing at where the first already moved things.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/adbf2c1b4c995fab. Report an issue: GitHub.