diff --git a/docker_init.bash b/docker_init.bash index 247bc9f8..4cbc4467 100644 --- a/docker_init.bash +++ b/docker_init.bash @@ -185,8 +185,17 @@ chown_home_hermeswebui() { # macOS Docker bind mounts can expose hermes-agent git object packs as # read-only host files. The runtime only needs to read those existing objects; # requiring chown on them makes startup fail before WebUI can run (#2237). + # + # Multi-container compose (#2470) additionally mounts the entire + # hermes-agent-src volume read-only on the WebUI side because the WebUI only + # reads it for `uv pip install`. On a :ro mount, chown returns EROFS for any + # file inside the subtree, which would propagate to `set -e` and kill startup + # before the WebUI can run. Either way, the WebUI never writes to the agent + # source — prune the entire hermes-agent path from the chown walk so a + # read-only or partially-read-only mount doesn't break the rest of the home + # ownership alignment. find /home/hermeswebui \ - -path "/home/hermeswebui/.hermes/hermes-agent/.git/objects" -prune \ + -path "/home/hermeswebui/.hermes/hermes-agent" -prune \ -o -exec chown -h "${WANTED_UID}:${WANTED_GID}" {} + } diff --git a/tests/test_issue2237_docker_chown_git_objects.py b/tests/test_issue2237_docker_chown_git_objects.py index 99a69829..3788736f 100644 --- a/tests/test_issue2237_docker_chown_git_objects.py +++ b/tests/test_issue2237_docker_chown_git_objects.py @@ -8,13 +8,40 @@ REPO = Path(__file__).resolve().parents[1] INIT_SCRIPT = (REPO / "docker_init.bash").read_text(encoding="utf-8") -def test_home_chown_skips_hermes_agent_git_objects(): +def test_home_chown_skips_hermes_agent_subtree(): + """The chown walk must skip the entire hermes-agent mount, not just + .git/objects. The original #2237 issue was macOS bind mounts exposing + read-only `.git/objects` packs; the post-v0.51.83 multi-container compose + setup additionally mounts the whole hermes-agent source tree :ro on the + WebUI side (#2470). Either failure mode breaks the chown walk under + `set -e`; pruning the parent path covers both.""" assert "chown_home_hermeswebui()" in INIT_SCRIPT - assert "/home/hermeswebui/.hermes/hermes-agent/.git/objects" in INIT_SCRIPT - assert "-prune" in INIT_SCRIPT + # The prune target should be the whole hermes-agent subtree, not just + # the inner `.git/objects` directory. The old narrower prune was + # insufficient once the entire mount became :ro. + assert "-path \"/home/hermeswebui/.hermes/hermes-agent\" -prune" in INIT_SCRIPT, ( + "chown walk must prune the entire hermes-agent path (not just " + ".git/objects) so a :ro multi-container mount doesn't EROFS-fail " + "the chown." + ) assert 'chown -h "${WANTED_UID}:${WANTED_GID}"' in INIT_SCRIPT +def test_home_chown_helper_documents_readonly_mount_compat(): + """The prune comment must reference the :ro multi-container scenario so + future maintainers don't narrow it back to just .git/objects (which would + re-introduce the EROFS-on-startup failure for the multi-container setup).""" + chown_fn_start = INIT_SCRIPT.index("chown_home_hermeswebui()") + chown_fn_end = INIT_SCRIPT.index("\n}\n", chown_fn_start) + fn_block = INIT_SCRIPT[chown_fn_start:chown_fn_end] + + assert "read-only" in fn_block.lower() or "ro" in fn_block.lower(), ( + "chown_home_hermeswebui must document why the entire hermes-agent " + "path is pruned (the :ro mount made the previous narrower prune " + "insufficient)." + ) + + def test_root_init_uses_git_object_safe_chown_helper(): root_start = INIT_SCRIPT.index('if [ "A${whoami}" == "Aroot" ]; then') root_restart = INIT_SCRIPT.index("exec su", root_start)