fix(docker): close the cold-container and multi-backend gaps in attachment delivery

Follow-ups on the salvaged commit:

1. get_cache_directory_mounts() now CREATES missing staging dirs instead of
   skipping them. Docker snapshots the mount list at container creation, so
   a dir born later (first attachment, first clipboard image) dangled for
   the life of a persistent container. Empty bind mount costs nothing.

2. to_agent_visible_cache_path() translates per-backend instead of
   docker-only: docker/modal -> /root/.hermes, ssh/daytona/vercel_sandbox ->
   ~/.hermes (shell-expanded remotely; bytes arrive via file sync), local/
   singularity keep the host path (apptainer auto-binds the host home).
   Mirrors the proven _agent_cache_base_for_env heuristics.

Updated the two mount-list tests pinning the old skip behavior; added
per-backend translation coverage.
This commit is contained in:
Teknium
2026-08-08 05:19:32 -07:00
parent 464e7e4e5f
commit fe54ab4f98
3 changed files with 129 additions and 75 deletions

View File

@@ -355,12 +355,22 @@ class TestCacheDirectoryMounts:
assert "/root/.hermes/cache/images" in container_paths
def test_empty_hermes_home(self, tmp_path, monkeypatch):
"""No cache dirs → empty list."""
"""Empty home → every staging dir is created and mounted (#76577).
Docker snapshots the mount list at container creation; skipping
not-yet-existing dirs meant the first attachment/clipboard file after
container start dangled forever. All _CACHE_DIRS entries mount."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
assert get_cache_directory_mounts() == []
mounts = get_cache_directory_mounts()
container_paths = {m["container_path"] for m in mounts}
assert "/root/.hermes/attachments" in container_paths
assert "/root/.hermes/images" in container_paths
assert "/root/.hermes/cache/images" in container_paths
for mount in mounts:
assert Path(mount["host_path"]).is_dir()
def test_images_upload_dir_is_mounted(self, tmp_path, monkeypatch):
"""The flat top-level ``images/`` upload dir is mounted (#69575).
@@ -412,12 +422,55 @@ class TestMapCachePathToContainer:
)
def test_returns_none_when_no_cache_dirs_exist(self, tmp_path, monkeypatch):
def test_maps_path_even_when_cache_dir_missing(self, tmp_path, monkeypatch):
"""Missing staging dirs are auto-created at mount-list time (#76577):
Docker snapshots mounts at container creation, so a dir that appears
later would dangle for the container's whole life. The map must
therefore succeed (and the dir exist) even before first use."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
assert map_cache_path_to_container(str(hermes_home / "cache" / "images" / "x.png")) is None
mapped = map_cache_path_to_container(str(hermes_home / "cache" / "images" / "x.png"))
assert mapped == "/root/.hermes/cache/images/x.png"
assert (hermes_home / "cache" / "images").is_dir()
class TestToAgentVisiblePathPerBackend:
"""#76577 follow-up: translation covers every backend that relocates the
Hermes cache — not just docker — and skips the ones where the host path
stays correct (local; singularity auto-binds the host home)."""
def _staged(self, tmp_path, monkeypatch):
hermes_home = tmp_path / ".hermes"
(hermes_home / "attachments").mkdir(parents=True)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
return str(hermes_home / "attachments" / "drop.zip")
def test_docker_maps_to_root_hermes(self, tmp_path, monkeypatch):
staged = self._staged(tmp_path, monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "docker")
from tools.credential_files import to_agent_visible_cache_path
assert to_agent_visible_cache_path(staged) == "/root/.hermes/attachments/drop.zip"
def test_ssh_maps_to_tilde_hermes(self, tmp_path, monkeypatch):
staged = self._staged(tmp_path, monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "ssh")
from tools.credential_files import to_agent_visible_cache_path
assert to_agent_visible_cache_path(staged) == "~/.hermes/attachments/drop.zip"
@pytest.mark.parametrize("backend", ["local", "singularity", ""])
def test_untranslated_backends_keep_host_path(self, tmp_path, monkeypatch, backend):
staged = self._staged(tmp_path, monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", backend)
from tools.credential_files import to_agent_visible_cache_path
assert to_agent_visible_cache_path(staged) == staged
def test_non_cache_path_passes_through(self, tmp_path, monkeypatch):
self._staged(tmp_path, monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "docker")
from tools.credential_files import to_agent_visible_cache_path
assert to_agent_visible_cache_path("/etc/hosts") == "/etc/hosts"
class TestIterCacheFiles: