# NousResearch/hermes-agent · Mixed: uv root install permissions and unrelated fix

## Open now

- 0 issues, 1 PR open on GitHub.
- PR #27124: fix(update): print snapshot restore hint so users can recover state (#26737)

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.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 1 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #33467. Verify it addresses your case.
- **Recommended:** read PR #33467 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-33467`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #21457: \[Bug\]: Root-mode Installation error with uv python path

## Prior & competing PRs

- **#33467: fix(install): set world-readable uv python dirs for root FHS layout (salvages #22494)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `scripts/install.sh`, `tests/test_install_sh_root_fhs_uv_python_path.py`
  - fix hunks:
  - `scripts/install.sh`

    ```diff
    @@ -268,10 +268,18 @@ resolve_install_layout() {
             fi
             INSTALL_DIR="/usr/local/lib/hermes-agent"
             ROOT_FHS_LAYOUT=true
    +        # Place uv-managed Python under /usr/local/share so the venv interpreter
    +        # is world-readable.  Default uv paths land in /root/.local/share/uv,
    +        # which non-root users can't traverse — leaving the shared
    +        # /usr/local/bin/hermes wrapper unable to exec the bad-interpreter venv
    +        # python.  See #21457.
    +        export UV_PYTHON_INSTALL_DIR="${UV_PYTHON_INSTALL_DIR:-/usr/local/share/uv/python}"
    +        export UV_PYTHON_BIN_DIR="${UV_PYTHON_BIN_DIR:-/usr/local/share/uv/bin}"
             log_info "Root install on Linux — using FHS layout"
             log_info "  Code:    $INSTALL_DIR"
             log_info "  Command: /usr/local/bin/hermes"
             log_info "  Data:    $HERMES_HOME (unchanged)"
    +        log_info "  uv Python: $UV_PYTHON_INSTALL_DIR (world-readable)"
             return 0
         fi
     
    ```
  - `tests/test_install_sh_root_fhs_uv_python_path.py`

    ```diff
    @@ -0,0 +1,59 @@
    +"""Regression test for install.sh root-mode uv Python install path.
    +
    +When installing as root with the FHS layout (INSTALL_DIR=/usr/local/lib/...),
    +``uv python install`` must place the managed Python under a world-readable
    +location, otherwise the venv interpreter ends up at ``/root/.local/share/uv/...``
    +and the shared ``/usr/local/bin/hermes`` wrapper fails for non-root users with
    +"bad interpreter: Permission denied".  See #21457.
    +"""
    +
    +from pathlib import Path
    +
    +
    +REPO_ROOT = Path(__file__).resolve().parent.parent
    +INSTALL_SH = REPO_ROOT / "scripts" / "install.sh"
    +
    +
    +def _resolve_install_layout_body() -> str:
    +    """Return just the body of resolve_install_layout(), bounded by its
    +    opening signature and the next top-level ``}`` close brace.
    +
    +    Using the function body (not "first ``return 0`` after a marker") guards
    +    the tests below against future refactors that hoist the export above
    +    another conditional with its own early-return, or that insert an early-
    +    return between the marker and the export — both of which would leave the
    +    export unreachable while a less-strict assertion still passed.
    +    """
    +    text = INSTALL_SH.read_text(encoding="utf-8")
    +    head, _, rest = text.partition("resolve_install_layout() {\n")
    +    assert rest, "Could not find resolve_install_layout() in scripts/install.sh"
    +    body, _, _ = rest.partition("\n}\n")
    +    assert body, "Could not find resolve_install_layout() closing brace"
    +    return body
    +
    +
    +def test_root_fhs_layout_exports_world_readable_uv_python_dirs() -> None:
    +    text = INSTALL_SH.read_text(encoding="utf-8")
    +
    +    assert 'export UV_PYTHON_INSTALL_DIR="${UV_PYTHON_INSTALL_DIR:-/usr/local/share/uv/python}"' in text
    +    assert 'export UV_PYTHON_BIN_DIR="${UV_PYTHON_BIN_DIR:-/usr/local/share/uv/bin}"' in text
    +
    +
    +def test_root_fhs_uv_python_export_is_inside_root_branch() -> None:
    +    """The export must live in the root-FHS branch of resolve_install_layout,
    +    after ``ROOT_FHS_LAYOUT=true`` and before the branch's ``return 0``, so
    +    non-root and Termux installs are unaffected. Bound the slice by the
    +    function body (not "next return 0" in the whole file) so the assertion
    +    can't accept an unreachable export."""
    +    body = _resolve_install_layout_body()
    +
    +    marker = 'ROOT_FHS_LAYOUT=true'
    +    assert marker in body
    +    after_marker = body.split(marker, 1)[1]
    +    return_idx = after_marker.find('return 0')
    +    export_idx = after_marker.find('UV_PYTHON_INSTALL_DIR')
    +    assert export_idx != -1, "UV_PYTHON_INSTALL_DIR export missing from root-FHS branch"
    +    assert return_idx != -1, "root-FHS branch must end with `return 0`"
    +    assert export_idx < return_idx, (
    +        "Export must precede the branch's `return 0` — otherwise unreachable"
    +    )
    ```
- **#22494: fix(install): set world-readable uv python dirs for root FHS layout**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`
- **#27124: fix(update): print snapshot restore hint so users can recover state (#26737)**. open
  - maintainer direction: category `partial_fix`; implication `needs_llm`
  - files: `hermes_cli/main.py`

## Fix status

- Merged to main in PR #33467 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-27`.
- Subsystem: `hermes_cli`
