unslothai/unsloth · error · ValueError

Resume checkpoint must be inside Unsloth outputs.

Error message

Resume checkpoint must be inside Unsloth outputs.

What it means

Raised when the resolved resume directory is not located under the Unsloth outputs root. After resolving the path, _is_under_outputs (resume.py:22) checks path.resolve().relative_to(outputs_root()); anything outside that root — /tmp, /data, another user's home — is rejected. The restriction keeps resume operations sandboxed to runs this studio produced, preventing accidental (or malicious) resume from arbitrary directories.

Source

Thrown at studio/backend/core/training/resume.py:224

            str(checkpoint)
            for checkpoint in checkpoints
            if _checkpoint_step(checkpoint) >= 0
            and is_resume_checkpoint_valid(checkpoint, expected_step)
        ),
        None,
    )


def normalize_resume_output_dir(path_value: str) -> str:
    if _is_foreign_absolute_path(path_value):
        raise ValueError("Resume checkpoint uses a path from a different operating system.")
    try:
        path = resolve_output_dir(path_value)
        path.resolve(strict = True)
    except (OSError, RuntimeError) as error:
        raise ValueError("Resume checkpoint path could not be resolved.") from error
    if not _is_under_outputs(path):
        raise ValueError("Resume checkpoint must be inside Unsloth outputs.")
    return str(path)


def training_run_config(run: dict) -> dict:
    raw_config = run.get("config_json")
    if isinstance(raw_config, dict):
        return raw_config
    if not isinstance(raw_config, str) or not raw_config.strip():
        return {}
    try:
        parsed = json.loads(raw_config)
    except (json.JSONDecodeError, TypeError):
        return {}
    return parsed if isinstance(parsed, dict) else {}


def _uses_s3_dataset(run: dict) -> bool:
    config = training_run_config(run)

View on GitHub (pinned to 203007d190)

Solutions

  1. Move or copy the checkpoint directory under the Unsloth outputs root, then pass its new path.
  2. If the run originally lived under outputs, pass the original path instead of a copy elsewhere.
  3. Check for a misconfigured outputs root (UNSLOTH outputs dir env/config) if the checkpoint really should qualify.

Example fix

// before
normalize_resume_output_dir("/tmp/copied-checkpoint")
// after
import shutil
shutil.move("/tmp/copied-checkpoint", str(outputs_root() / "imported-run"))
normalize_resume_output_dir(str(outputs_root() / "imported-run"))
Defensive patterns

Strategy: validation

Validate before calling

from utils.paths import outputs_root
from pathlib import Path

def under_outputs(p: str) -> bool:
    try:
        Path(p).resolve(strict=True).relative_to(outputs_root().resolve(strict=False))
        return True
    except (OSError, RuntimeError, ValueError):
        return False

assert under_outputs(resume_path), "checkpoint must live under outputs root"

Try / catch

try:
    normalized = normalize_resume_output_dir(path_value)
except ValueError as e:
    if "inside Unsloth outputs" in str(e):
        # move/copy the checkpoint under outputs_root() and retry
        ...

Prevention

When it happens

Trigger: Calling normalize_resume_output_dir('/tmp/my-checkpoint') or any absolute/relative path that resolves outside outputs_root(); symlinks inside outputs that point outside are also caught because _is_under_outputs compares resolved paths.

Common situations: User downloads/copies a checkpoint from another machine into /tmp or ~ and tries to resume from there; checkpoint hand-placed in a shared data volume; path traversal attempts like 'outputs/../../etc/checkpoint' (resolved away by resolve()).

Related errors


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