fix(setup): send the HF token when installing a gated model

Installing a gated model failed the fast download path with "401
Unauthorized" even with a valid token and the licence accepted, then fell
back to snapshot_download and logged a 401 that reads like the token or
the licence grant is at fault when it is neither (#2163).

`token_resolver.resolve()` returns a ResolvedToken record, not the bearer
string. Two call sites handed that record straight to consumers typed
`token: str | None`, and both fail silently rather than loudly:

- `_segmented_snapshot` passes it to HfApi, get_hf_file_metadata and our
  own segmented_download. huggingface_hub's build_hf_headers ignores a
  non-str token and falls back to its own ambient discovery, so a token
  held only in VoiceStudio's Settings produces NO Authorization header
  and every gated file 401s. segmented_download instead interpolates it
  into `f"Bearer {token}"`, sending a malformed header that also inlines
  the raw secret into the request.
- `_step_fetch_weights` passes it to snapshot_download, so gated engine
  weights 401 the same way.

Every other resolve() caller already unwraps `.token`; these two were the
outliers. Both now unwrap once, at the seam.

The existing weights tests all stubbed resolve() to return None, so no
test ever exercised a resolved token — which is why this went unnoticed.
The new tests drive a real ResolvedToken through both seams and assert a
`str` reaches every consumer, plus an integration test that installs the
pyannote diarisation pipeline end to end: a weightless config_only repo
validates, both dependency repos are fetched, and every call carries the
bearer string.

Two catalogue invariants keep the rest of #2163 from returning by edit:
dependency repos must be revision-pinned (revision_for raises otherwise,
so an unpinned one ships an always-failing install), and a config_only
entry must declare config_required_files (without them the completeness
check can never pass and the error lists no files at all — the shape the
report hit on 0.5.2).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Shivendra-Coherent
2026-09-17 16:50:16 +05:30
co-authored by Claude Opus 5
parent 4e55180f70
commit 37b498c891
8 changed files with 400 additions and 5 deletions
+10 -1
View File
@@ -230,7 +230,16 @@ def _segmented_snapshot(repo_id: str, *, endpoint: "str | None", revision: str)
from services.segmented_download import segmented_download
from services.token_resolver import resolve as _resolve_token
token = _resolve_token()
# `resolve()` returns a ResolvedToken record, not the bearer string, and
# every consumer below is typed `token: str | None`. Handing over the
# record fails silently rather than loudly (#2163): huggingface_hub's
# build_hf_headers ignores a non-str token and falls back to its own
# ambient discovery, so a token held only in VoiceStudio's settings sends
# NO Authorization header at all and every gated file 401s; our own
# segmented_download interpolates it into `f"Bearer {token}"` and sends a
# malformed header carrying the raw secret. Unwrap once, here.
_resolved = _resolve_token()
token = _resolved.token if _resolved else None
api = HfApi(endpoint=endpoint, token=token)
info = api.repo_info(repo_id, repo_type="model", revision=revision)
commit = info.sha
+6 -1
View File
@@ -1543,10 +1543,15 @@ def _step_fetch_weights(spec: SidecarSpec, job: dict) -> None:
# other model download in the app — see setup/download.py): the
# source checkout is unpinned upstream `main` anyway, and hf_hub
# checksum-verifies each artifact. Hence the B615 waiver below.
# Unwrap to the bearer string: snapshot_download takes `token: str |
# None`, and a ResolvedToken record is ignored in favour of ambient
# discovery, so gated engine weights 401 for a user whose token lives
# in VoiceStudio's settings rather than HF's own cache (#2163).
_resolved = resolve_token()
kwargs: dict = {
"repo_id": spec.weights_repo_id,
"local_dir": str(wdir),
"token": resolve_token(),
"token": _resolved.token if _resolved else None,
}
if spec.weights_revision:
kwargs["revision"] = spec.weights_revision