Send HUGGING_FACE_TOKEN for public but gated Hugging Face repos - #538
Merged
Merged
Conversation
This was referenced Sep 3, 2026
download_huggingface_dataset only authenticated when ModelInfo.private was true. A public but gated repo reports private=False with gated="auto" or "manual", so the helper passed token=None and the Hub answered 401 unless the caller happened to export HF_TOKEN. Treat a repo as requiring authentication when private is true or gated is truthy, and pass the HUGGING_FACE_TOKEN through the existing get_or_prompt_hf_token() path. Public, ungated repos still download with token=None and never prompt, and non-interactive runs without secrets still pass None rather than an empty string. Fixes #529. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
fix/hf-gated-repo-token
branch
from
September 29, 2026 12:14
fe14370 to
493b930
Compare
MaxGhenis
added a commit
that referenced
this pull request
Sep 29, 2026
With #538, a public but gated repo (private=False, gated="manual", as policyengine/policyengine-uk-data-private is) also requires authentication, so it takes the same path that now warns when no HUGGING_FACE_TOKEN resolves. Add a "gated" lookup to TestNoTokenWarning: the three non-interactive no-token environments must warn, and an environment token must reach hf_hub_download without a warning. With the private-only predicate restored, the four new gated cases fail; with the warning removed, all ten warning-asserting cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Merge audit (d051, Max ruled 2026-09-29 to land this fix).
|
MaxGhenis
added a commit
that referenced
this pull request
Sep 29, 2026
download_huggingface_dataset keeps passing token=None through to hf_hub_download when a repo that requires authentication (private, or gated since #538) yields no HUGGING_FACE_TOKEN (per #422: huggingface_hub then applies its own cached token, so `hf auth login` users keep working), but now emits a UserWarning first so that the bare 401 huggingface_hub raises when that fallback is empty too can be traced to the missing or unapproved token (#529). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 29, 2026
With #538, a public but gated repo (private=False, gated="manual", as policyengine/policyengine-uk-data-private is) also requires authentication, so it takes the same path that now warns when no HUGGING_FACE_TOKEN resolves. Add a "gated" lookup to TestNoTokenWarning: the three non-interactive no-token environments must warn, and an environment token must reach hf_hub_download without a warning. With the private-only predicate restored, the four new gated cases fail; with the warning removed, all ten warning-asserting cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 29, 2026
…vely Fold in the non-blocking findings from the independent review of #540: - Say a 401 means no token was sent and a 403 means a gated repo has not approved the token; huggingface_hub raises GatedRepoError for both and documents the unapproved case as 403. - Say the fallback to huggingface_hub's cached token is what normally happens: HF_HUB_DISABLE_IMPLICIT_TOKEN turns it off. - Name `huggingface-cli login` as well as `hf auth login`: the `hf` command first ships in huggingface_hub 0.34, and pyproject still allows 0.25.1. - Assert the warning in the two older no-token tests that now emit it, and give the ungated never-prompts test's getpass mock a string return value so a widened predicate fails on the assertion, not on os.environ. - Reword the changelog fragment: after #538 the repo may be private or gated, and a cached token that is not approved gives a 403. Add TestTokenRoutingInvariants, which runs every combination of repo state (ungated, gated None, fields missing, gated auto, gated manual, private, not found), environment (token unset, empty, only HF_TOKEN, set), TTY and prompt entry (112 cases) through the real function and checks it against a spec written out in the test: the token passed on, whether getpass is called, that the token is never "", and that exactly one warning fires when a repo requiring authentication ends up with no token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 30, 2026
download_huggingface_dataset keeps passing token=None through to hf_hub_download when a repo that requires authentication (private, or gated since #538) yields no HUGGING_FACE_TOKEN (per #422: huggingface_hub then applies its own cached token, so `hf auth login` users keep working), but now emits a UserWarning first so that the bare 401 huggingface_hub raises when that fallback is empty too can be traced to the missing or unapproved token (#529). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 30, 2026
With #538, a public but gated repo (private=False, gated="manual", as policyengine/policyengine-uk-data-private is) also requires authentication, so it takes the same path that now warns when no HUGGING_FACE_TOKEN resolves. Add a "gated" lookup to TestNoTokenWarning: the three non-interactive no-token environments must warn, and an environment token must reach hf_hub_download without a warning. With the private-only predicate restored, the four new gated cases fail; with the warning removed, all ten warning-asserting cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 30, 2026
…vely Fold in the non-blocking findings from the independent review of #540: - Say a 401 means no token was sent and a 403 means a gated repo has not approved the token; huggingface_hub raises GatedRepoError for both and documents the unapproved case as 403. - Say the fallback to huggingface_hub's cached token is what normally happens: HF_HUB_DISABLE_IMPLICIT_TOKEN turns it off. - Name `huggingface-cli login` as well as `hf auth login`: the `hf` command first ships in huggingface_hub 0.34, and pyproject still allows 0.25.1. - Assert the warning in the two older no-token tests that now emit it, and give the ungated never-prompts test's getpass mock a string return value so a widened predicate fails on the assertion, not on os.environ. - Reword the changelog fragment: after #538 the repo may be private or gated, and a cached token that is not approved gives a 403. Add TestTokenRoutingInvariants, which runs every combination of repo state (ungated, gated None, fields missing, gated auto, gated manual, private, not found), environment (token unset, empty, only HF_TOKEN, set), TTY and prompt entry (112 cases) through the real function and checks it against a spec written out in the test: the token passed on, whether getpass is called, that the token is never "", and that exactly one warning fires when a repo requiring authentication ends up with no token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 30, 2026
…_TOKEN (#540) * Warn when a restricted Hugging Face download resolves no token download_huggingface_dataset keeps passing token=None through to hf_hub_download when a repo that requires authentication (private, or gated since #538) yields no HUGGING_FACE_TOKEN (per #422: huggingface_hub then applies its own cached token, so `hf auth login` users keep working), but now emits a UserWarning first so that the bare 401 huggingface_hub raises when that fallback is empty too can be traced to the missing or unapproved token (#529). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Cover gated repos in the no-token warning tests With #538, a public but gated repo (private=False, gated="manual", as policyengine/policyengine-uk-data-private is) also requires authentication, so it takes the same path that now warns when no HUGGING_FACE_TOKEN resolves. Add a "gated" lookup to TestNoTokenWarning: the three non-interactive no-token environments must warn, and an environment token must reach hf_hub_download without a warning. With the private-only predicate restored, the four new gated cases fail; with the warning removed, all ten warning-asserting cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tighten the no-token warning text and pin the token contract exhaustively Fold in the non-blocking findings from the independent review of #540: - Say a 401 means no token was sent and a 403 means a gated repo has not approved the token; huggingface_hub raises GatedRepoError for both and documents the unapproved case as 403. - Say the fallback to huggingface_hub's cached token is what normally happens: HF_HUB_DISABLE_IMPLICIT_TOKEN turns it off. - Name `huggingface-cli login` as well as `hf auth login`: the `hf` command first ships in huggingface_hub 0.34, and pyproject still allows 0.25.1. - Assert the warning in the two older no-token tests that now emit it, and give the ungated never-prompts test's getpass mock a string return value so a widened predicate fails on the assertion, not on os.environ. - Reword the changelog fragment: after #538 the repo may be private or gated, and a cached token that is not approved gives a 403. Add TestTokenRoutingInvariants, which runs every combination of repo state (ungated, gated None, fields missing, gated auto, gated manual, private, not found), environment (token unset, empty, only HF_TOKEN, set), TTY and prompt entry (112 cases) through the real function and checks it against a spec written out in the test: the token passed on, whether getpass is called, that the token is never "", and that exactly one warning fires when a repo requiring authentication ends up with no token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cover private-and-gated repos and count prompts in the token grid Add a "private-gated" repo state (private=True, gated="manual") to TestTokenRoutingInvariants, so a predicate that is true for private or gated but false for both (for example an xor) now fails 16 cases instead of passing all of them. Assert getpass's call_count rather than whether it was called, so a double prompt fails 30 grid cases, not one side test. The grid grows from 112 to 128 cases. Also give the Warns: docstring the same `huggingface-cli login` caveat the runtime warning already carries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the 403, GatedRepoError and huggingface-cli parts of the warning text The non-interactive warning test now also asserts that the message names RepositoryNotFoundError, GatedRepoError, the 403 case and the pre-0.34 `huggingface-cli login` command. Each of those clauses could previously be deleted without failing a test; each deletion now fails 9 of 167. The TestNoTokenWarning docstring no longer calls the 401 "missing or unapproved": with an empty fallback there is no token to be unapproved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #529.
Summary
download_huggingface_datasetdecided whether to send a token by testingModelInfo.privateonly.policyengine/policyengine-uk-data-privatehas been public but gated (private=False,gated="manual") since 31 July 2026, so the helper passedtoken=Nonetohf_hub_downloadand the gate returned401 GatedRepoErroreven when a gate-approvedHUGGING_FACE_TOKENwas in the environment. The Hub only falls back to its ownHF_TOKENvariable when no explicit token is passed, which is why PolicyEngine/policyengine-uk#1817 had to export both names as a workaround (see the comment in.github/workflows/code_changes.yamlandpr_code_changes.yamlthere).Behaviour change
A repo now requires authentication when
privateis true orgatedis truthy.ModelInfo.gatedisFalsefor ungated repos,"auto"or"manual"for gated repos, andNonewhen the field is absent, so a truthiness test covers every state. In that case the existingget_or_prompt_hf_token()supplies the token fromHUGGING_FACE_TOKEN(or an interactive prompt on a TTY).Unchanged:
token=Noneand never prompt.Nonerather than an empty string (the Handle empty HUGGING_FACE_TOKEN gracefully #422 behaviour), so Dependabot-style CI keeps working for public repos.RepositoryNotFoundErrorpath still treats the repo as private.HUGGING_FACE_TOKENunset, a gated repo now prompts for a token exactly as a private repo always has. Pressing Enter passestoken=None, and huggingface_hub then applies its own implicit token (HF_TOKENor the login file) if one is configured.Verified live:
GET /api/models/policyengine/policyengine-uk-data-privatereturnsprivate: false, gated: "manual", and an unauthenticatedHEADon a resolve URL returns 401.Tests
New tests in
tests/core/tools/test_hugging_face.py:test_download_gated_public_repo_passes_env_token[manual|auto]mocksmodel_inforeturningprivate=False, gated=...and asserts the token read fromHUGGING_FACE_TOKENby the realget_or_prompt_hf_tokenreacheshf_hub_download. Both fail onmasterand pass here.test_download_private_flag_repo_passes_env_tokencoversprivate=Truereturned bymodel_info(the existing private test only covers the 404 path).test_download_gated_repo_non_interactive_without_tokenasserts CI without secrets getstoken=Noneand no prompt.test_download_public_ungated_repo_never_prompts[False|None]guards against widening the predicate into a prompt on every public download.Checks on the rebased head (493b930, on master a0ccbda):
Args:/Returns:), type hints andfrom pathlib import Path, and Fix vacuous test for private Hugging Face repo download without a token #539's parametrisedtest_download_private_repo_no_tokenbyte-for-byte; the only removed lines are the fouris_repo_privatelines this PR renames torequires_authentication.uv run --frozen pytest tests/core/tools/test_hugging_face.py -q: 23 passed.hugging_face.pyswapped in, the twotest_download_gated_public_repo_passes_env_tokencases fail and the other 21 pass.uvx ruff format --check .anduvx ruff checkon the changed files pass.Invariants (checked by an independent reviewer over a 168-case grid of
private×gated× environment × TTY, 0 violations):hf_hub_downloadis eitherNoneor a non-empty string, never"".token=Noneand never prompts.HUGGING_FACE_TOKENalways gets that token, without a prompt.Not run locally: mypy and the full suite (CI covers them), and
make documentation(no docs changed).axiom: n/a: infrastructure (Hugging Face download authentication), no policy change.
Related
🤖 Generated with Claude Code