{"repo":"NousResearch/hermes-agent","public_id":"21457","display_label":"Mixed: uv root install permissions and unrelated fix","summary":"Two members address world-readable permissions for uv Python directories in root/FHS installations. The remaining member is an unrelated change about printing snapshot restore hints during updates.","subsystem":"hermes_cli","counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":1,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":33467,"merged_at":"2026-05-27T20:55:51+00:00"},"resolution":{"state":"resolved","by":"pr-33467"},"freshness":{"state":"verified","last_verified_at":"2026-05-27T20:55:51+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":21457,"pr_number":33467,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-27T20:55:51+00:00"}],"members":[{"kind":"issue","number":21457,"title":"[Bug]: Root-mode Installation error with uv python path","url":null,"state":"closed"},{"kind":"pr","number":22494,"title":"fix(install): set world-readable uv python dirs for root FHS layout","url":null,"state":"closed","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":27124,"title":"fix(update): print snapshot restore hint so users can recover state (#26737)","url":null,"state":"open","close_reason":{"category":"partial_fix","implication":"needs_llm"},"merged":false},{"kind":"pr","number":33467,"title":"fix(install): set world-readable uv python dirs for root FHS layout (salvages #22494)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["scripts/install.sh","tests/test_install_sh_root_fhs_uv_python_path.py"],"fix_hunks":[{"file":"scripts/install.sh","hunks":["@@ -268,10 +268,18 @@ resolve_install_layout() {\n         fi\n         INSTALL_DIR=\"/usr/local/lib/hermes-agent\"\n         ROOT_FHS_LAYOUT=true\n+        # Place uv-managed Python under /usr/local/share so the venv interpreter\n+        # is world-readable.  Default uv paths land in /root/.local/share/uv,\n+        # which non-root users can't traverse — leaving the shared\n+        # /usr/local/bin/hermes wrapper unable to exec the bad-interpreter venv\n+        # python.  See #21457.\n+        export UV_PYTHON_INSTALL_DIR=\"${UV_PYTHON_INSTALL_DIR:-/usr/local/share/uv/python}\"\n+        export UV_PYTHON_BIN_DIR=\"${UV_PYTHON_BIN_DIR:-/usr/local/share/uv/bin}\"\n         log_info \"Root install on Linux — using FHS layout\"\n         log_info \"  Code:    $INSTALL_DIR\"\n         log_info \"  Command: /usr/local/bin/hermes\"\n         log_info \"  Data:    $HERMES_HOME (unchanged)\"\n+        log_info \"  uv Python: $UV_PYTHON_INSTALL_DIR (world-readable)\"\n         return 0\n     fi\n "]},{"file":"tests/test_install_sh_root_fhs_uv_python_path.py","hunks":["@@ -0,0 +1,59 @@\n+\"\"\"Regression test for install.sh root-mode uv Python install path.\n+\n+When installing as root with the FHS layout (INSTALL_DIR=/usr/local/lib/...),\n+``uv python install`` must place the managed Python under a world-readable\n+location, otherwise the venv interpreter ends up at ``/root/.local/share/uv/...``\n+and the shared ``/usr/local/bin/hermes`` wrapper fails for non-root users with\n+\"bad interpreter: Permission denied\".  See #21457.\n+\"\"\"\n+\n+from pathlib import Path\n+\n+\n+REPO_ROOT = Path(__file__).resolve().parent.parent\n+INSTALL_SH = REPO_ROOT / \"scripts\" / \"install.sh\"\n+\n+\n+def _resolve_install_layout_body() -> str:\n+    \"\"\"Return just the body of resolve_install_layout(), bounded by its\n+    opening signature and the next top-level ``}`` close brace.\n+\n+    Using the function body (not \"first ``return 0`` after a marker\") guards\n+    the tests below against future refactors that hoist the export above\n+    another conditional with its own early-return, or that insert an early-\n+    return between the marker and the export — both of which would leave the\n+    export unreachable while a less-strict assertion still passed.\n+    \"\"\"\n+    text = INSTALL_SH.read_text(encoding=\"utf-8\")\n+    head, _, rest = text.partition(\"resolve_install_layout() {\\n\")\n+    assert rest, \"Could not find resolve_install_layout() in scripts/install.sh\"\n+    body, _, _ = rest.partition(\"\\n}\\n\")\n+    assert body, \"Could not find resolve_install_layout() closing brace\"\n+    return body\n+\n+\n+def test_root_fhs_layout_exports_world_readable_uv_python_dirs() -> None:\n+    text = INSTALL_SH.read_text(encoding=\"utf-8\")\n+\n+    assert 'export UV_PYTHON_INSTALL_DIR=\"${UV_PYTHON_INSTALL_DIR:-/usr/local/share/uv/python}\"' in text\n+    assert 'export UV_PYTHON_BIN_DIR=\"${UV_PYTHON_BIN_DIR:-/usr/local/share/uv/bin}\"' in text\n+\n+\n+def test_root_fhs_uv_python_export_is_inside_root_branch() -> None:\n+    \"\"\"The export must live in the root-FHS branch of resolve_install_layout,\n+    after ``ROOT_FHS_LAYOUT=true`` and before the branch's ``return 0``, so\n+    non-root and Termux installs are unaffected. Bound the slice by the\n+    function body (not \"next return 0\" in the whole file) so the assertion\n+    can't accept an unreachable export.\"\"\"\n+    body = _resolve_install_layout_body()\n+\n+    marker = 'ROOT_FHS_LAYOUT=true'\n+    assert marker in body\n+    after_marker = body.split(marker, 1)[1]\n+    return_idx = after_marker.find('return 0')\n+    export_idx = after_marker.find('UV_PYTHON_INSTALL_DIR')\n+    assert export_idx != -1, \"UV_PYTHON_INSTALL_DIR export missing from root-FHS branch\"\n+    assert return_idx != -1, \"root-FHS branch must end with `return 0`\"\n+    assert export_idx < return_idx, (\n+        \"Export must precede the branch's `return 0` — otherwise unreachable\"\n+    )\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}