pypi huggingface-hub 1.20.0
[v1.20.0] Browser-based OAuth login, multi-commit folder uploads, and more

4 hours ago

๐Ÿ”’ Browser-based OAuth login

hf auth login now defaults to a browser-based OAuth Device Code flow instead of asking you to copy-paste a token. The command prints a URL and a short code, you authorize in the browser, and the CLI retrieves and saves the token for you. The same applies to login() in Python. In an interactive terminal you still get a gh-style arrow-key menu to pick between browser login and pasting a token, and --token works exactly as before.

OAuth tokens expire after 30 days, but they come with a refresh token: get_token() transparently refreshes them when less than a day of validity remains, so long-running setups keep working without re-authenticating. hf auth list now shows the expiry date for OAuth tokens.

> hf auth login
? How would you like to log in? Log in with your browser

    Open this URL in your browser:
        https://hf.co/oauth/device

    And enter the code: 52AT-FLYZ

    Waiting for authorization.

When the command is run by an AI agent, it never prompts. Instead it streams structured events so the agent can surface the URL and code to its user, then blocks until a terminal auth_success / auth_error event:

$ hf auth login --format json
{"event": "device_code", "verification_uri": "https://hf.co/oauth/device", "user_code": "52AT-FLYZ", "verification_uri_complete": "https://hf.co/oauth/device", "expires_in": 300, "interval": 5}
{"event": "auth_success", "user": "celinah", "token_name": "oauth-celinah"}

hf auth list surfaces the new expiry column:

$ hf auth list
  name          token       expires
- ------------- ----------- -------------------
  my-token      hf_****5678
* oauth-user    hf_****1234 2026-07-09
  oauth-old     hf_****9999 2026-06-09 (expired)

Finally, notebook_login() now renders the link and code with plain IPython.display.HTML, dropping the ipywidgets dependency.

โšก Faster, more reliable hf upload for large folders

hf upload and the underlying upload_folder have been revamped to be faster and far more robust on large folders. When hf_xet is installed (the default), uploads now run through a streamed, multi-commit pipeline built on the XetSession API: the folder is scanned and fed into a background Xet upload while previous batches are committed in parallel, and files are hashed in a single read pass while they are chunked (the old flow read every large file twice). Nothing changes in how you call it:

hf upload <repo-id> <path/to/folder>

This is a drop-in replacement for experimental hf upload-large-folder used until today, which will be deprecated in a future release.

๐Ÿšจ๐Ÿšจ Breaking change: With the upload_folder and hf upload revamp, uploading a folder might result in multiple commits. It is also not possible to open a PR against a specific revision while using upload_folder. If you pass create_pr=True, it will necessarily create a PR against main. It will open the PR no matter if some changes have been committed (previously an empty commit was resulting in no PR opened at all).

What you get on large folders:

  • More reliable. Uploads are resumable and stateless. If an upload is interrupted, just re-run the same command: already-committed files are detected and skipped, and already-uploaded chunks are deduplicated by the Xet backend (โ‰ˆ0 bytes re-transferred). There are no local state files to go stale, so resume even works from a different machine.
  • Faster. Files are hashed while being chunked (single read pass) and batches commit in the background while the next batch is already uploading, so there is no separate hashing phase blocking the upload.
  • Multi-commit by default. Large folders are automatically split into adaptive commits that scale between 64 and 1024 files based on commit duration. Folders that fit in a single batch still produce exactly one commit, as before; follow-up commits get a (part N) suffix.
  • Live progress bar tracking the preparing, uploading, and committing stages (with a plain-log fallback when output is not a TTY):
Found 301 files to upload
  Preparing   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ  301 / 301 โœ“
  Uploading   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘  255 / 300 files  25.5MB ยท 1.86MB/s
  Committing  โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘  0 / 301

upload_large_folder / hf upload-large-folder are intentionally left untouched in this release; their deprecation will follow once hf upload has fully absorbed the use case.

  • [Upload] Streamed multi-commit upload_folder powered by Xet by @Wauplin in #4331

๐Ÿ’ป Jobs: wait, SSH access, and cleaner error messages

This release adds three major capabilities to Hugging Face Jobs.

Wait for completion. HfApi.wait_for_job() and hf jobs wait block until one or more Jobs reach a terminal stage, which makes it easy to chain commands in CI scripts. wait_for_job accepts a single id or a list, returns the final JobInfo even on failure (check job.status.stage), and only raises TimeoutError on timeout. The CLI exits 0 only if all waited-on Jobs ended COMPLETED.

# Wait on a single job, then run the next step only if it succeeded
hf jobs wait <job_id> && next-step

# Wait on a batch, with a timeout
hf jobs wait <id1> <id2> --timeout 10m

โš ๏ธ Breaking change: non-detached hf jobs run / hf jobs uv run now exit with the Job's outcome (exit code 1 if the Job errored) instead of always exiting 0. We consider this a bugfix โ€” scripts relying on the old behavior were being silently misled โ€” but it is called out here in case you depend on the previous exit code.

SSH access. With --ssh at launch and an SSH key registered on huggingface.co/settings/keys, you can connect straight into a running Job's container with hf jobs ssh <job_id>. Thanks to wait_for_job, hf jobs ssh now waits for the Job to reach RUNNING before connecting (with a status spinner) instead of failing immediately while it is still scheduling.

$ hf jobs run --ssh --detach python:3.12 sleep infinity
โœ“ Job started
  id: 6a33ba2aef9220ea67d98a03
  url: https://huggingface.co/jobs/Wauplin/6a33ba2aef9220ea67d98a03
Hint: Use `hf jobs ssh Wauplin/6a33ba2aef9220ea67d98a03` to open an SSH session into the job.

$ hf jobs ssh Wauplin/6a33ba2aef9220ea67d98a03
Job is running.
Running `ssh 6a33ba2aef9220ea67d98a03@ssh.hf.jobs`
root@j-wauplin-6a33ba2aef9220ea67d98a03-do4bduvn-5f153-458k4:/#

Readable errors. A new JobNotFoundError and the switch from response.raise_for_status to hf_raise_for_status turn raw httpx tracebacks into clean, actionable messages. Per-command try/except blocks were removed in favor of the global CLI error handling.

$ hf jobs inspect 000
Error: 404 Client Error. (Request ID: Root=1-6a316470-...)

Job Not Found for url: https://huggingface.co/api/jobs/Wauplin/000.
Please make sure you specified the correct job ID and namespace.
Set HF_DEBUG=1 as environment variable for full traceback.
  • [Jobs] Add hf jobs wait and HfApi.wait_for_job by @Wauplin in #4345
  • [Jobs] Add SSH support to run a Job and connect to it by @Wauplin in #4352
  • [CLI] Make hf jobs ssh wait for job to be running by @Wauplin in #4379
  • Add new JobNotFound error by @Wauplin in #4367

๐Ÿ–ฅ๏ธ Custom-container deploy for Inference Endpoints

hf endpoints deploy can now deploy custom Docker containers end-to-end, no more hand-writing JSON and POSTing the raw endpoints API. New flags wire up the image and its runtime: --custom-image, --health-route, --port, --command, and --container-args. Environment variables and secrets can be injected with --env/--env-file and --secrets/--secrets-file. On the SDK side, create_inference_endpoint gains container_command and container_args parameters.

hf endpoints deploy nex-n2-pro \
  --repo nex-agi/Nex-N2-Pro \
  --framework custom \
  --accelerator gpu --vendor aws --region us-east-1 \
  --instance-type nvidia-h200 --instance-size x8 \
  --custom-image nexagi/sglang:v0.5.12 \
  --health-route /health --port 30000 \
  --container-args "--reasoning-parser qwen3 --tool-call-parser qwen3_coder --mamba-scheduler-strategy extra_buffer --tp 8" \
  --env MODEL_ID=/repository \
  --type authenticated

The type parameter now defaults to authenticated instead of the deprecated protected (passing protected emits a FutureWarning). The custom-container flags raise a clean error if used without --custom-image.

  • [Inference Endpoints] Custom-container deploy CLI + deprecate protected endpoint type by @gary149 in #4329

โณ Wait for a Space with wait_for_space and hf spaces wait

Mirroring the new wait_for_job primitive, HfApi.wait_for_space() and hf spaces wait block until a Space leaves an intermediate stage (BUILDING, APP_STARTING, โ€ฆ) and settles on a final state. The CLI exits 0 if the Space is RUNNING, non-zero otherwise. hf spaces ssh and hf spaces dev-mode were refactored to use wait_for_space internally instead of the old CLI-only helper.

# Wait after a restart
hf spaces restart username/my-space && hf spaces wait username/my-space

# With a timeout
hf spaces wait username/my-space --timeout 5m
>>> from huggingface_hub import restart_space, wait_for_space
>>> restart_space("username/my-space")
>>> runtime = wait_for_space("username/my-space")
>>> runtime.stage
'RUNNING'

๐Ÿ“š Documentation: CLI guide โ€” wait for a Space ยท Space runtime reference

  • [Spaces] Add wait_for_space API and hf spaces wait CLI by @Wauplin in #4380

๐Ÿ’” Breaking Changes

๐Ÿšจ๐Ÿšจ With the upload_folder and hf upload revamp, uploading a folder might result in multiple commits.
It is also not possible to open a PR against a specific revision while using upload_folder. If you pass create_pr=True, it will necessarily create a PR against main. It will open the PR no matter if some changes have been committed (previously an empty commit was resulting in no PR opened at all).

  • [Upload] Streamed multi-commit upload_folder powered by Xet in #4331 by @Wauplin

RepoUrl now rejects canonical single-segment repo IDs like "gpt2" or "datasets/squad" (use "user/gpt2" or "datasets/user/squad" instead). repo_type_and_id_from_hf_id is softly deprecated. parse_hf_uri gains an endpoint argument to parse URLs from self-hosted Hub instances.

  • [URIs] Use parse_hf_uri in RepoUrl + soft-deprecate repo_type_and_id_from_hf_id by @Wauplin in #4324

Non-detached hf jobs run / hf jobs uv run now exit with the Job's outcome (exit 1 on Job error) instead of always exiting 0

  • [Jobs] Add hf jobs wait and HfApi.wait_for_job by @Wauplin in #4345 โ€” see the Jobs highlight above.

๐Ÿ–ฅ๏ธ CLI

  • [CLI] Suggest creating repo/bucket on NotFound errors by @Wauplin in #4372 โ€” when a repo or bucket can't be found, the CLI now hints at the matching create command instead of just reporting the 404.

๐Ÿ”ง Other QoL Improvements

  • [HTTP] Retry on HTTP 408 Request Timeout by default by @Wauplin in #4360 โ€” 408 Request Timeout is now part of the default retry status set, alongside the existing 5xx codes.

๐Ÿ“– Documentation

๐Ÿ› Bug and typo fixes

  • OIDC: Include error_description in HTTP error messages by @coyotte508 in #4341 โ€” failed OIDC exchanges now surface the server's error_description, making misconfigured Trusted Publishers far easier to debug.
  • [Download] Retry on RemoteProtocolError in http_get by @Wauplin in #4351 โ€” transient connection drops mid-download are now retried instead of failing the download.
  • Ignore Windows metadata files in cache scan by @Chinmay1220 in #4357 โ€” desktop.ini and similar Windows metadata files no longer trip up scan-cache.

๐Ÿ—๏ธ Internal

  • [Tests] Add inference pytest marker to filter inference tests by @Wauplin in #4338
  • Post-release: bump version to 1.20.0.dev0 by @huggingface-hub-bot[bot] in #4342
  • [Tests] Mitigate transient CI failures by @Wauplin in #4343
  • [CI] Fix versioned docs not publishing on release by @hanouticelina in #4344
  • [Release] Skip --prerelease=allow injection for sentence-transformers by @hanouticelina in #4366
  • [CI] Do not fail on tmp repo deletion by @Wauplin in #4371
  • Bump codecov/codecov-action from 6.0.1 to 7.0.0 in the actions group by @dependabot[bot] in #4373

Don't miss a new huggingface-hub release

NewReleases is sending notifications on new releases.