unslothai/unsloth · error · RuntimeError

{spec.name} source revision must be a lowercase full Git com

Error message

{spec.name} source revision must be a lowercase full Git commit

What it means

First check in ensure_pinned_source: spec.revision must be a full 40-char lowercase hex Git commit SHA — it must match _REVISION_PATTERN and equal its own .lower() (i.e. no uppercase). The library pins immutable commits, not mutable refs, and normalizes nothing for you: the check runs before any cache lookup or download.

Source

Thrown at studio/backend/utils/third_party_source.py:777

            if hashlib.sha256(destination_file.read_bytes()).hexdigest() != expected_digest:
                raise RuntimeError(f"{spec.name} source changed while preparing its runtime")
        for relative, content in _generated_file_contents(spec).items():
            destination_file = staging / relative
            destination_file.parent.mkdir(parents = True, exist_ok = True)
            destination_file.write_bytes(content)
        if not _valid_runtime(staging, spec, checkout):
            raise RuntimeError(f"The prepared {spec.name} runtime failed integrity validation")
        _replace_owned_directory(staging, runtime)
    finally:
        _remove_owned_path(workspace)


def ensure_pinned_source(
    spec: PinnedSource, *, legacy_sources: tuple[Path | str, ...] = ()
) -> Path:
    revision = spec.revision.lower()
    if _REVISION_PATTERN.fullmatch(revision) is None or revision != spec.revision:
        raise RuntimeError(f"{spec.name} source revision must be a lowercase full Git commit")
    for digest in (spec.source_tree_digest, spec.runtime_tree_digest):
        if digest is not None and _SHA256_PATTERN.fullmatch(digest) is None:
            raise RuntimeError(f"{spec.name} source digest must be a lowercase SHA-256")
    if (spec.source_tree_digest is None) != (spec.runtime_tree_digest is None):
        raise RuntimeError(f"{spec.name} source and runtime digests must be configured together")

    parent = cache_root() / "third-party-sources" / spec.name
    version_root = parent / revision
    checkout = version_root / "source"
    runtime = version_root / "runtime-v1"
    if _valid_runtime(runtime, spec):
        return runtime.resolve()

    version_root.mkdir(parents = True, exist_ok = True)
    try:
        with FileLock(str(parent / ".install.lock"), timeout = 300):
            if _valid_runtime(runtime, spec):
                return runtime.resolve()

View on GitHub (pinned to 203007d190)

Solutions

  1. Resolve the ref to a full commit: git rev-parse <tag>^{commit} and use that 40-char lowercase SHA as revision
  2. Normalize before constructing the spec: revision=revision.strip().lower() and assert 40 hex chars
  3. If you intended a floating ref, that is unsupported — pins must be immutable commits

Example fix

# before
PinnedSource(name="foo", package="foo", revision="v1.2.3", ...)

# after
sha = subprocess.check_output(["git", "ls-remote", url, "refs/tags/v1.2.3"]).split()[0].decode()
PinnedSource(name="foo", package="foo", revision=sha.lower(), ...)
Defensive patterns

Strategy: validation

Validate before calling

import re
_REVISION_PATTERN = re.compile(r"[0-9a-f]{40}")
assert _REVISION_PATTERN.fullmatch(spec.revision), (
    f"revision {spec.revision!r} must be a full 40-char lowercase hex commit SHA"
)

Type guard

import re

def is_full_commit_sha(revision: object) -> bool:
    return isinstance(revision, str) and re.fullmatch(r"[0-9a-f]{40}", revision) is not None

Prevention

When it happens

Trigger: Passing revision as a tag/branch name ('v1.2.3', 'main'), a short SHA ('1a2b3c4'), an uppercase SHA ('1A2B...'), or a string with whitespace. The lowercase(pattern-match) + equality-with-original construction rejects any uppercase input explicitly.

Common situations: Copy-pasting a tag instead of the commit it resolves to; tooling that emits short SHAs; UIs uppercasing hex; config files generated from git describe output.

Related errors


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