From a023dd4ea3c79f1b39bfe15eb8f5f621738ca477 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Fri, 3 Apr 2026 19:20:15 +0000 Subject: [PATCH 01/12] [CLI] Space hot-reload: wait for up-to-date infos --- src/huggingface_hub/cli/spaces.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 3f62988d98..22fdfc6e6b 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -277,6 +277,10 @@ def spaces_hot_reload( raise CLIError(f"Unable to read sdk_version from {space_id} cardData") if version.parse(sdk_version) < version.Version(HOT_RELOADING_MIN_GRADIO): raise CLIError(f"Hot-reloading requires Gradio >= {HOT_RELOADING_MIN_GRADIO} (found {sdk_version})") + if (current_sha := space_info.sha) is None: + raise CLIError(f"Unexpected `None` running SHA for Space {space_id}") + else: + current_sha = None if local_file: local_path = local_file @@ -329,6 +333,7 @@ def spaces_hot_reload( _spaces_hot_reload_summary( api=api, space_id=space_id, + current_sha=current_sha, commit_sha=commit_info.oid, local_path=local_path if local_file else os.path.basename(local_path), token=token, @@ -338,11 +343,18 @@ def spaces_hot_reload( def _spaces_hot_reload_summary( api: HfApi, space_id: str, + current_sha: str | None, commit_sha: str, local_path: str | None, token: str | None, ) -> None: - space_info = api.space_info(space_id) + while (space_info := api.space_info(space_id)).sha == current_sha: + if current_sha is None: + break + typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK) + time.sleep(2) + if space_info.sha != commit_sha: + raise CLIError(f"Expected SHA {commit_sha} after hot-reload but got {space_info.sha}") if (runtime := space_info.runtime) is None: raise CLIError(f"Unable to read SpaceRuntime from {space_id} infos") if (hot_reloading := runtime.hot_reloading) is None: From dd2c0ea7cb0b51ffbb63d3c067287d27925618f9 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Fri, 3 Apr 2026 19:38:06 +0000 Subject: [PATCH 02/12] Assert parent commit when possible --- src/huggingface_hub/cli/spaces.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 22fdfc6e6b..88a0c9a6e9 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -326,6 +326,7 @@ def spaces_hot_reload( repo_id=space_id, path_or_fileobj=local_path, path_in_repo=filename, + parent_commit=current_sha, _hot_reload=True, ) From 2751eae1a094ae37c09e972f34fe706ad89323d5 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 7 Apr 2026 12:54:09 +0200 Subject: [PATCH 03/12] Update src/huggingface_hub/cli/spaces.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: célina --- src/huggingface_hub/cli/spaces.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 88a0c9a6e9..836336004f 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -349,10 +349,11 @@ def _spaces_hot_reload_summary( local_path: str | None, token: str | None, ) -> None: + status = StatusLine() while (space_info := api.space_info(space_id)).sha == current_sha: if current_sha is None: break - typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK) + status.update("Waiting for up-to-date Space info...") time.sleep(2) if space_info.sha != commit_sha: raise CLIError(f"Expected SHA {commit_sha} after hot-reload but got {space_info.sha}") From 0cfb780dc513508e8ade8fa38d9a0a373f712c92 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 11:06:28 +0000 Subject: [PATCH 04/12] Increase httpx timeout from 5s (default) to 10s --- src/huggingface_hub/_hot_reload/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/huggingface_hub/_hot_reload/client.py b/src/huggingface_hub/_hot_reload/client.py index 2d6cd702bd..0405ec061c 100644 --- a/src/huggingface_hub/_hot_reload/client.py +++ b/src/huggingface_hub/_hot_reload/client.py @@ -27,6 +27,7 @@ HOT_RELOADING_PORT = 7887 +CLIENT_TIMEOUT = 10 class MultiReplicaStreamEvent(TypedDict): @@ -57,6 +58,7 @@ def __init__( self.client = httpx.Client( base_url=f"{base_host}/--replicas/+{replica_hash}", headers=build_hf_headers(token=token), + timeout=CLIENT_TIMEOUT, ) def get_reload(self, reload_id: str) -> Iterator[ApiGetReloadEventSourceData]: From a0ceaea62a2ef01feaf853392da4615678fedcfb Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 14:02:12 +0000 Subject: [PATCH 05/12] Revert "Update src/huggingface_hub/cli/spaces.py" This reverts commit 2751eae1a094ae37c09e972f34fe706ad89323d5. --- src/huggingface_hub/cli/spaces.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 836336004f..88a0c9a6e9 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -349,11 +349,10 @@ def _spaces_hot_reload_summary( local_path: str | None, token: str | None, ) -> None: - status = StatusLine() while (space_info := api.space_info(space_id)).sha == current_sha: if current_sha is None: break - status.update("Waiting for up-to-date Space info...") + typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK) time.sleep(2) if space_info.sha != commit_sha: raise CLIError(f"Expected SHA {commit_sha} after hot-reload but got {space_info.sha}") From ad42bffa8da4d780aa1df95a439a0cb64b3d63b4 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 14:04:15 +0000 Subject: [PATCH 06/12] Write to stderr --- src/huggingface_hub/cli/spaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 88a0c9a6e9..01cfed7103 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -352,7 +352,7 @@ def _spaces_hot_reload_summary( while (space_info := api.space_info(space_id)).sha == current_sha: if current_sha is None: break - typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK) + typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK, err=True) time.sleep(2) if space_info.sha != commit_sha: raise CLIError(f"Expected SHA {commit_sha} after hot-reload but got {space_info.sha}") From ae8ef8e8f2891cdd4bbcf8b88f4ae5c403739f70 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 14:58:28 +0000 Subject: [PATCH 07/12] local_file is a PATH --- src/huggingface_hub/cli/spaces.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 01cfed7103..5b39bebe1a 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -33,6 +33,7 @@ import sys import tempfile import time +from pathlib import Path from typing import Annotated, Literal, get_args import typer @@ -235,7 +236,7 @@ def spaces_hot_reload( ), ] = None, local_file: Annotated[ - str | None, + Path | None, typer.Option( "--local-file", "-f", @@ -283,8 +284,8 @@ def spaces_hot_reload( current_sha = None if local_file: - local_path = local_file - filename = local_file if filename is None else filename + local_path = str(local_file) + filename = local_path if filename is None else filename elif filename: if not skip_checks: try: @@ -336,7 +337,7 @@ def spaces_hot_reload( space_id=space_id, current_sha=current_sha, commit_sha=commit_info.oid, - local_path=local_path if local_file else os.path.basename(local_path), + local_path=local_path if local_file else filename, token=token, ) From a081b62cc239ac7d405c02f6663fd842b2ff1b1c Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 15:26:37 +0000 Subject: [PATCH 08/12] Docs update --- docs/source/en/package_reference/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/en/package_reference/cli.md b/docs/source/en/package_reference/cli.md index 25c90d7c7d..17f6625529 100644 --- a/docs/source/en/package_reference/cli.md +++ b/docs/source/en/package_reference/cli.md @@ -3414,7 +3414,7 @@ $ hf spaces hot-reload [OPTIONS] SPACE_ID [FILENAME] **Options**: -* `-f, --local-file TEXT`: Path of local file. Interactive editor mode if not specified +* `-f, --local-file PATH`: Path of local file. Interactive editor mode if not specified * `--skip-checks / --no-skip-checks`: Skip hot-reload compatibility checks. [default: no-skip-checks] * `--skip-summary / --no-skip-summary`: Skip summary display after hot-reload is triggered [default: no-skip-summary] * `--token TEXT`: A User Access Token generated from https://huggingface.co/settings/tokens. From 6867c507793463063e1a7ee7aca2a8fb92b896d7 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 15:27:02 +0000 Subject: [PATCH 09/12] Much more Agent friendly --- src/huggingface_hub/cli/spaces.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 5b39bebe1a..e0688a270b 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -314,7 +314,13 @@ def spaces_hot_reload( enable_progress_bars() editor_res = _editor_open(local_path) if editor_res == "no-tty": - raise CLIError("Cannot open an editor (no TTY). Use -f flag to hot-reload from local path") + persistent_temp_dir = tempfile.mkdtemp() + shutil.copytree(temp_dir.name, persistent_temp_dir, dirs_exist_ok=True) + local_path = os.path.join(persistent_temp_dir, filename) + typer.secho("No TTY detected. Non-interactive fallback:") + typer.secho(f"- Edit {local_path}") + typer.secho(f"- Run `hf spaces hot-reload {space_id} {filename} -f {local_path}`") + return if editor_res == "no-editor": raise CLIError("No editor found in local environment. Use -f flag to hot-reload from local path") if editor_res != 0: From 0c756d9175192c39e8a81e16dfac37d3d521c826 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 15:43:09 +0000 Subject: [PATCH 10/12] Fix skipped commit scenario --- src/huggingface_hub/cli/spaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index e0688a270b..d37f56134a 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -357,7 +357,7 @@ def _spaces_hot_reload_summary( token: str | None, ) -> None: while (space_info := api.space_info(space_id)).sha == current_sha: - if current_sha is None: + if current_sha is None or current_sha == commit_sha: break typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK, err=True) time.sleep(2) From 47d616ab51379b274e6493ad856b10b8f43b5de7 Mon Sep 17 00:00:00 2001 From: cbensimon Date: Tue, 7 Apr 2026 15:43:30 +0000 Subject: [PATCH 11/12] git pull hint when relevant --- src/huggingface_hub/cli/spaces.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index d37f56134a..4641130879 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -337,7 +337,12 @@ def spaces_hot_reload( _hot_reload=True, ) + if local_file is not None and local_file.resolve().is_relative_to(Path.cwd()): + typer.secho(f"Created commit {commit_info.oid} in remote Space repository.") + typer.secho("Consider running `git pull --autostash` to stay synced if you are working from a local clone.") + if not skip_summary: + typer.secho("Hot-reload summary:") _spaces_hot_reload_summary( api=api, space_id=space_id, From afea80d2e3143d268536a18610637803365337fd Mon Sep 17 00:00:00 2001 From: cbensimon Date: Wed, 8 Apr 2026 10:11:40 +0000 Subject: [PATCH 12/12] Fix 'Path type change breaks path_in_repo on Windows' --- src/huggingface_hub/cli/spaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 4641130879..9b6fd7002d 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -285,7 +285,7 @@ def spaces_hot_reload( if local_file: local_path = str(local_file) - filename = local_path if filename is None else filename + filename = local_file.as_posix() if filename is None else filename elif filename: if not skip_checks: try: