From ad2af0b242ab595f3a1c45364b473f508224335a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 14 Jan 2026 17:06:39 +0000 Subject: [PATCH 1/3] fix: Support Dockerfile.hud in environment directory detection and build The `hud debug .` command was failing when a directory had `Dockerfile.hud` instead of `Dockerfile`. This fix: - Adds `find_dockerfile()` function that prefers `Dockerfile.hud` over `Dockerfile` - Updates `is_environment_directory()` to recognize both Dockerfile variants - Updates `build_environment()` to use the `-f` flag when using `Dockerfile.hud` - Adds comprehensive tests for the new behavior Fixes HUD-592 Co-authored-by: ryantan --- hud/cli/utils/environment.py | 40 ++++++++++++++++++++++-- hud/cli/utils/tests/test_environment.py | 41 ++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/hud/cli/utils/environment.py b/hud/cli/utils/environment.py index bb2e17c55..f5b2b245b 100644 --- a/hud/cli/utils/environment.py +++ b/hud/cli/utils/environment.py @@ -78,12 +78,22 @@ def build_environment(directory: str | Path, image_name: str, no_cache: bool = F Returns: True if build succeeded, False otherwise """ + dir_path = Path(directory) + dockerfile_path = find_dockerfile(dir_path) + build_cmd = ["docker", "build", "-t", image_name] + + # Specify the Dockerfile path if using Dockerfile.hud + if dockerfile_path is not None and dockerfile_path.name != "Dockerfile": + build_cmd.extend(["-f", str(dockerfile_path)]) + if no_cache: build_cmd.append("--no-cache") build_cmd.append(str(directory)) hud_console.info(f"🔨 Building image: {image_name}{' (no cache)' if no_cache else ''}") + if dockerfile_path is not None and dockerfile_path.name != "Dockerfile": + hud_console.info(f"Using {dockerfile_path.name}") hud_console.info("") # Empty line before Docker output # Just run Docker build directly - it has its own nice live display @@ -110,11 +120,35 @@ def image_exists(image_name: str) -> bool: return result.returncode == 0 +def find_dockerfile(directory: Path) -> Path | None: + """Find the Dockerfile in a directory, preferring Dockerfile.hud. + + Checks for Dockerfile.hud first (HUD-specific), then falls back to Dockerfile. + + Args: + directory: Directory to search in + + Returns: + Path to the Dockerfile if found, None otherwise + """ + # Prefer Dockerfile.hud for HUD environments + hud_dockerfile = directory / "Dockerfile.hud" + if hud_dockerfile.exists(): + return hud_dockerfile + + # Fall back to standard Dockerfile + standard_dockerfile = directory / "Dockerfile" + if standard_dockerfile.exists(): + return standard_dockerfile + + return None + + def is_environment_directory(path: str | Path) -> bool: """Check if a path looks like an environment directory. An environment directory should have: - - A Dockerfile + - A Dockerfile (Dockerfile.hud or Dockerfile) - A pyproject.toml file - Optionally a src directory """ @@ -122,8 +156,8 @@ def is_environment_directory(path: str | Path) -> bool: if not dir_path.is_dir(): return False - # Must have Dockerfile - if not (dir_path / "Dockerfile").exists(): + # Must have Dockerfile.hud or Dockerfile + if find_dockerfile(dir_path) is None: return False # Must have pyproject.toml diff --git a/hud/cli/utils/tests/test_environment.py b/hud/cli/utils/tests/test_environment.py index 0baa5583e..920c1bd5f 100644 --- a/hud/cli/utils/tests/test_environment.py +++ b/hud/cli/utils/tests/test_environment.py @@ -3,7 +3,12 @@ from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch -from hud.cli.utils.environment import get_image_name, image_exists, is_environment_directory +from hud.cli.utils.environment import ( + find_dockerfile, + get_image_name, + image_exists, + is_environment_directory, +) if TYPE_CHECKING: from pathlib import Path @@ -36,6 +41,40 @@ def test_is_environment_directory(tmp_path: Path): assert is_environment_directory(d) is True +def test_is_environment_directory_with_dockerfile_hud(tmp_path: Path): + """Test that Dockerfile.hud is recognized as a valid environment directory.""" + d = tmp_path / "env" + d.mkdir() + assert is_environment_directory(d) is False + # Use Dockerfile.hud instead of Dockerfile + (d / "Dockerfile.hud").write_text("FROM python:3.11") + assert is_environment_directory(d) is False + (d / "pyproject.toml").write_text("[tool.hud]") + assert is_environment_directory(d) is True + + +def test_find_dockerfile_prefers_dockerfile_hud(tmp_path: Path): + """Test that Dockerfile.hud is preferred over Dockerfile.""" + d = tmp_path / "env" + d.mkdir() + # No Dockerfile + assert find_dockerfile(d) is None + # Add Dockerfile + (d / "Dockerfile").write_text("FROM python:3.11") + assert find_dockerfile(d) == d / "Dockerfile" + # Add Dockerfile.hud - should now be preferred + (d / "Dockerfile.hud").write_text("FROM python:3.12") + assert find_dockerfile(d) == d / "Dockerfile.hud" + + +def test_find_dockerfile_only_dockerfile_hud(tmp_path: Path): + """Test that Dockerfile.hud alone is found.""" + d = tmp_path / "env" + d.mkdir() + (d / "Dockerfile.hud").write_text("FROM python:3.11") + assert find_dockerfile(d) == d / "Dockerfile.hud" + + @patch("subprocess.run") def test_image_exists_true(mock_run): mock_run.return_value = MagicMock(returncode=0) From 2eeb1d47c984ad47b94f070fa2bbb9c9a2c4a4e0 Mon Sep 17 00:00:00 2001 From: ryantzr1 Date: Thu, 15 Jan 2026 10:20:16 +0800 Subject: [PATCH 2/3] add support for Dockerfile.hud --- hud/cli/__init__.py | 13 +++++-- hud/cli/tests/test_debug_directory_mode.py | 40 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 hud/cli/tests/test_debug_directory_mode.py diff --git a/hud/cli/__init__.py b/hud/cli/__init__.py index 8488db3b2..e73390792 100644 --- a/hud/cli/__init__.py +++ b/hud/cli/__init__.py @@ -194,9 +194,9 @@ def debug( from .utils.environment import ( build_environment, + find_dockerfile, get_image_name, image_exists, - is_environment_directory, ) hud_console = HUDConsole() @@ -224,8 +224,15 @@ def debug( first_param = params[0] docker_args = params[1:] if len(params) > 1 else [] - # Check if it's a directory - if Path(first_param).exists() and is_environment_directory(first_param): + # Check if it's a directory (folder-mode). + # + # NOTE: `hud debug` should support env directories that only have + # `Dockerfile.hud` (or `Dockerfile`) even if they don't have a + # `pyproject.toml` yet. Using the stricter `is_environment_directory()` + # would incorrectly treat `.` as an image name, leading to: + # docker: invalid reference format + p = Path(first_param) + if p.exists() and p.is_dir() and find_dockerfile(p) is not None: # Directory mode - like hud dev directory = first_param diff --git a/hud/cli/tests/test_debug_directory_mode.py b/hud/cli/tests/test_debug_directory_mode.py new file mode 100644 index 000000000..91ffd7d34 --- /dev/null +++ b/hud/cli/tests/test_debug_directory_mode.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from pathlib import Path + + +def test_hud_debug_directory_mode_accepts_dockerfile_hud_without_pyproject( + tmp_path: Path, monkeypatch +) -> None: + """ + Regression test for: + - `hud debug .` treating '.' as an image name when the directory contains + `Dockerfile.hud` but no `pyproject.toml`, resulting in: + docker: invalid reference format + """ + # Simulate a minimal environment directory (no pyproject.toml yet) + (tmp_path / "Dockerfile.hud").write_text("FROM python:3.11\n", encoding="utf-8") + + # Run from inside the env dir, matching `hud debug .` + monkeypatch.chdir(tmp_path) + + import hud.cli.__init__ as cli + from hud.cli.utils import environment as env_utils + + # Avoid interactive prompts/builds during the test + monkeypatch.setattr(env_utils, "image_exists", lambda _image: True) + + captured: dict[str, object] = {} + + async def _fake_debug_mcp_stdio(command, logger, max_phase: int = 5) -> int: # type: ignore[no-untyped-def] + captured["command"] = command + return max_phase + + monkeypatch.setattr(cli, "debug_mcp_stdio", _fake_debug_mcp_stdio) + + # If directory detection fails, command would be: ["docker", "run", ..., "."] + cli.debug(params=["."], config=None, cursor=None, build=False, max_phase=1) + + command = captured["command"] + assert isinstance(command, list) + assert command[-1] == f"{tmp_path.name}:dev" From 1f2b32faf4a29f67135f03f9cc7a74d39da9d3f7 Mon Sep 17 00:00:00 2001 From: ryantzr1 Date: Thu, 15 Jan 2026 11:22:04 +0800 Subject: [PATCH 3/3] fix pytest --- hud/cli/__init__.py | 8 +------- hud/cli/tests/test_debug_directory_mode.py | 21 +++++++-------------- hud/cli/utils/environment.py | 13 +------------ 3 files changed, 9 insertions(+), 33 deletions(-) diff --git a/hud/cli/__init__.py b/hud/cli/__init__.py index e73390792..aa1bdb97d 100644 --- a/hud/cli/__init__.py +++ b/hud/cli/__init__.py @@ -224,13 +224,7 @@ def debug( first_param = params[0] docker_args = params[1:] if len(params) > 1 else [] - # Check if it's a directory (folder-mode). - # - # NOTE: `hud debug` should support env directories that only have - # `Dockerfile.hud` (or `Dockerfile`) even if they don't have a - # `pyproject.toml` yet. Using the stricter `is_environment_directory()` - # would incorrectly treat `.` as an image name, leading to: - # docker: invalid reference format + # Check if it's a directory with a Dockerfile p = Path(first_param) if p.exists() and p.is_dir() and find_dockerfile(p) is not None: # Directory mode - like hud dev diff --git a/hud/cli/tests/test_debug_directory_mode.py b/hud/cli/tests/test_debug_directory_mode.py index 91ffd7d34..183bb0261 100644 --- a/hud/cli/tests/test_debug_directory_mode.py +++ b/hud/cli/tests/test_debug_directory_mode.py @@ -1,27 +1,21 @@ from __future__ import annotations -from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path def test_hud_debug_directory_mode_accepts_dockerfile_hud_without_pyproject( tmp_path: Path, monkeypatch ) -> None: - """ - Regression test for: - - `hud debug .` treating '.' as an image name when the directory contains - `Dockerfile.hud` but no `pyproject.toml`, resulting in: - docker: invalid reference format - """ - # Simulate a minimal environment directory (no pyproject.toml yet) + """Test that hud debug . works with Dockerfile.hud and no pyproject.toml.""" (tmp_path / "Dockerfile.hud").write_text("FROM python:3.11\n", encoding="utf-8") - - # Run from inside the env dir, matching `hud debug .` monkeypatch.chdir(tmp_path) import hud.cli.__init__ as cli from hud.cli.utils import environment as env_utils - # Avoid interactive prompts/builds during the test monkeypatch.setattr(env_utils, "image_exists", lambda _image: True) captured: dict[str, object] = {} @@ -31,10 +25,9 @@ async def _fake_debug_mcp_stdio(command, logger, max_phase: int = 5) -> int: # return max_phase monkeypatch.setattr(cli, "debug_mcp_stdio", _fake_debug_mcp_stdio) - - # If directory detection fails, command would be: ["docker", "run", ..., "."] cli.debug(params=["."], config=None, cursor=None, build=False, max_phase=1) command = captured["command"] assert isinstance(command, list) - assert command[-1] == f"{tmp_path.name}:dev" + expected_name = tmp_path.name.replace("_", "-") + assert command[-1] == f"{expected_name}:dev" diff --git a/hud/cli/utils/environment.py b/hud/cli/utils/environment.py index f5b2b245b..4a5061984 100644 --- a/hud/cli/utils/environment.py +++ b/hud/cli/utils/environment.py @@ -121,22 +121,11 @@ def image_exists(image_name: str) -> bool: def find_dockerfile(directory: Path) -> Path | None: - """Find the Dockerfile in a directory, preferring Dockerfile.hud. - - Checks for Dockerfile.hud first (HUD-specific), then falls back to Dockerfile. - - Args: - directory: Directory to search in - - Returns: - Path to the Dockerfile if found, None otherwise - """ - # Prefer Dockerfile.hud for HUD environments + """Find Dockerfile in a directory, preferring Dockerfile.hud over Dockerfile.""" hud_dockerfile = directory / "Dockerfile.hud" if hud_dockerfile.exists(): return hud_dockerfile - # Fall back to standard Dockerfile standard_dockerfile = directory / "Dockerfile" if standard_dockerfile.exists(): return standard_dockerfile