fix(macos): re-land dylib-complete TCC interpreter anchor

The first landing (#95131/#95478, reverted in #95563) copied the
uv-store interpreter into venv/bin/python so TCC grants would stick
to a stable path. On real Macs that copy bricked every hermes command
two ways: dynamically-linked builds died in dyld because
@executable_path/../lib/libpython resolved into venv/lib/ (#95425),
and alias symlinks to the copy made CPython getpath lose the venv
prefix (#95541, ModuleNotFoundError: encodings).

Re-land:

- Keep the signed real-file copy of bin/python (identifier-pinned
  via _macos_sign_managed_python).
- Materialize python3 / python3.N as real-file copies, never
  symlinks. Copies boot on every build we could reproduce and keep
  the TCC identity.
- Hardlink store libpython* into venv/lib/ when present (copy across
  devices). Existing LC_RPATH already points there.
- Pre-install boot gate: launch the staged copy, demand encodings
  plus the venv prefix, abort and leave the live venv untouched
  on failure.

Doctor reports/installs the new anchor (the revert-era heal is
removed). Update refreshes it after a successful code swap. Tests
cover layout, idempotence, predecessor-symlink repair, libpython
hardlink, boot-gate refusal, and a macos_only real-interpreter E2E.

Closes #95596.
This commit is contained in:
Zheqing Zeng
2026-08-26 07:17:39 -07:00
committed by kshitij
parent f4ae8958b5
commit aa72df4b42
5 changed files with 859 additions and 135 deletions

View File

@@ -1154,54 +1154,33 @@ def _macos_desktop_dr(app: Path) -> str | None:
return (proc.stdout or "") + (proc.stderr or "")
def check_macos_tcc_anchor_removed() -> None:
"""Detect and repair a venv bricked by the reverted TCC anchor.
def check_macos_tcc_anchor(should_fix: bool = False) -> None:
"""Report (and optionally install) the dylib-complete TCC anchor (#95596).
The anchor (#95131/#95478, reverted) replaced ``venv/bin/python`` with a
real-file copy of the uv-store interpreter. On real Macs that copy could
not start: its ``LC_RPATH`` (``@executable_path/../lib``) resolved to
``venv/lib/``, which holds no libpython — every hermes command died in
dyld (#95425), and re-pointed aliases lost the stdlib (#95541). The
revert stops NEW anchors; this check heals venvs the anchor already
converted, by restoring ``bin/python`` to a symlink pointing at the
recorded source interpreter (the marker file the anchor wrote).
Silent on non-macOS and on venvs the anchor never touched.
Silent on non-macOS and for interpreters that are not uv-managed. Never
raises — a failed check must not crash doctor. Install is gated by the
module's pre-install boot probe, so ``--fix`` cannot brick the CLI.
"""
if sys.platform != "darwin":
return
# Resolved at call time via the module global so tests can retarget it.
root = Path(globals()["__file__"]).resolve().parents[1]
for name in ("venv", ".venv"):
venv_bin = root / name / "bin"
marker = venv_bin / ".tcc-anchor-source"
if not marker.is_file():
continue
try:
source = Path(marker.read_text(encoding="utf-8").strip())
venv_py = venv_bin / "python"
if source.is_file() and venv_py.is_file() and not venv_py.is_symlink():
tmp = venv_bin / ".python-unanchor-tmp"
tmp.unlink(missing_ok=True)
os.symlink(source, tmp)
os.replace(tmp, venv_py)
# Restore versioned aliases to point at bin/python.
for alias in venv_bin.glob("python3*"):
if alias.is_symlink() or alias.is_file():
alias_tmp = venv_bin / f".{alias.name}.unanchor-tmp"
alias_tmp.unlink(missing_ok=True)
os.symlink("python", alias_tmp)
os.replace(alias_tmp, alias)
marker.unlink(missing_ok=True)
check_ok(
"macOS TCC anchor removed",
f"({name}/bin/python restored to a symlink; the anchor "
"(#95425/#95541) is reverted)",
)
except Exception as e: # diagnostics must never crash
check_warn(
"macOS TCC anchor cleanup failed",
f"({e}) — restore manually: ln -sf $(cat {marker}) {venv_bin / 'python'}",
)
try:
from hermes_cli import macos_tcc_anchor as tcc
status, detail = tcc.tcc_anchor_state()
if status == "skip":
return
if status == "active":
check_ok("macOS TCC anchor active", f"({detail})")
return
if should_fix:
anchored = tcc.ensure_tcc_anchor()
if anchored is not None:
check_ok("macOS TCC anchor installed", f"({anchored})")
return
check_warn(
"macOS TCC anchor missing" if status == "missing" else "macOS TCC anchor stale",
f"({detail})",
)
except Exception as e: # diagnostics must never crash
check_warn("macOS TCC anchor check failed", f"({e})")
def check_macos_full_disk_access() -> None:
@@ -1423,10 +1402,9 @@ def run_doctor(args):
else:
check_warn("Not in virtual environment", "(recommended)")
# macOS TCC anchor REVERTED (#95425/#95541: anchored copies couldn't load
# libpython — every hermes command died in dyld). This heals venvs the
# anchor already converted. Silent on non-macOS.
check_macos_tcc_anchor_removed()
# macOS TCC interpreter anchor (#95596): dylib-complete re-land of the
# mechanism reverted in #95563. Silent on non-macOS.
check_macos_tcc_anchor(should_fix=should_fix)
# macOS Full Disk Access (issue #52010 follow-up): one grant silences
# every per-folder prompt permanently. Silent on non-macOS.

View File

@@ -0,0 +1,371 @@
"""Stable macOS TCC anchor for the uv-managed Python interpreter (#95596).
Re-land of the interpreter anchor reverted in #95563. macOS keys TCC grants
to the resolved absolute path of the client binary. Hermes' interpreter is
managed by uv and lives at a versioned store path; every patch bump orphans
every prior grant (#85345).
The first landing copied the interpreter into ``venv/bin/python`` but left
two holes that bricked real Macs:
* Dynamically-linked builds look up ``libpython`` via
``@executable_path/../lib``. That resolved into ``venv/lib/``, which had
no dylib — every hermes command, including update/doctor, died in dyld
(#95425).
* Alias names (``python3``, ``python3.N``) were re-pointed at the copy as
*symlinks*. Invoking the copied interpreter through a symlink makes
CPython getpath lose the venv prefix on affected python-build-standalone
builds — startup dies with ``ModuleNotFoundError: encodings`` and the
stdlib resolves to the build-time ``/install`` prefix (#95541). Console
scripts exec ``python3``, so the entire CLI surface died.
This re-land keeps the copy + identifier-pinned signature (TCC attribution
stays on the stable venv path) and closes both holes:
1. Aliases are materialized as real-file copies of the anchor, never
symlinks.
2. If the store ships ``libpython*``, it is hardlinked into ``venv/lib/``
(copy if the store is on another device). Existing ``LC_RPATH`` already
points at ``@executable_path/../lib`` — no rewrite.
3. A pre-install boot gate actually launches the staged copy and demands
``import encodings`` plus ``sys.prefix == <venv>``. Failure rolls the
staging file back and leaves the live venv untouched, so a bad anchor
can never brick update/doctor again.
All functions are no-ops on non-macOS and for interpreters that are not
uv-managed. Best-effort: never raises to callers.
"""
from __future__ import annotations
import logging
import os
import platform
import re
import shutil
import subprocess
import tempfile
from pathlib import Path
from hermes_constants import venv_python_path
logger = logging.getLogger(__name__)
_MARKER_NAME = ".tcc-anchor-source"
_STORE_COMMON_MARKERS = ("cpython-", "-macos-")
_STORE_ROOT_MARKERS = ("/uv/python/", "/.hermes-runtime/python/")
class _BootGateFailed(Exception):
"""Staged copy refused to boot; the live venv must stay untouched."""
def is_macos() -> bool:
return platform.system() == "Darwin"
def _sibling_names() -> tuple[str, ...]:
"""Alias names uv creates inside the venv bin dir."""
import sys as _sys
return ("python3", f"python3.{_sys.version_info.minor}")
def _store_bin_names() -> tuple[str, ...]:
"""Preferred interpreter file names inside a store ``bin`` dir."""
import sys as _sys
return (f"python3.{_sys.version_info.minor}", "python3", "python")
def _is_uv_macos_store(path: str) -> bool:
normalized = path.replace("\\", "/")
if not all(marker in normalized for marker in _STORE_COMMON_MARKERS):
return False
return any(marker in normalized for marker in _STORE_ROOT_MARKERS)
def _venv_dir(project_root: Path | None = None) -> Path | None:
root = (
Path(project_root)
if project_root is not None
else Path(__file__).resolve().parents[1]
)
for name in ("venv", ".venv"):
candidate = root / name
venv_py = venv_python_path(candidate)
if venv_py.is_file() or venv_py.is_symlink():
return candidate
return None
def _interpreter_file(src: str | Path) -> Path | None:
"""Return the interpreter binary file at/inside *src*."""
p = Path(src)
if p.is_file():
return p
if not p.is_dir():
return None
for name in _store_bin_names():
candidate = p / name
if candidate.is_file():
return candidate
try:
for candidate in sorted(p.glob("python3.*")):
if candidate.is_file() and not candidate.name.endswith((".dSYM", ".txt")):
return candidate
except OSError:
return None
return None
def _interpreter_source(venv_dir: Path) -> str | None:
"""Return the interpreter file the venv currently resolves to."""
venv_py = venv_python_path(venv_dir)
if venv_py.is_symlink():
try:
resolved = venv_py.resolve(strict=False)
except OSError:
return None
return str(resolved)
cfg = venv_dir / "pyvenv.cfg"
if not cfg.is_file():
return None
home = ""
try:
for line in cfg.read_text(encoding="utf-8").splitlines():
if line.lower().startswith("home"):
_, _, home = line.partition("=")
home = home.strip()
break
except OSError:
return None
if not home:
return None
interp = _interpreter_file(home)
return str(interp) if interp is not None else None
def _anchor_marker(venv_bin: Path) -> Path:
return venv_bin / _MARKER_NAME
def _store_root(source_file: Path) -> Path:
# .../cpython-<ver>-macos-*/bin/python3.N → store root
return source_file.resolve(strict=False).parent.parent
def _provision_libpython(
venv_dir: Path, source_file: Path, *, refresh: bool = False
) -> None:
"""Hardlink (else copy) store ``libpython*`` into ``venv/lib/``.
Provision-if-present: a surplus hardlink on a statically-linked build is
free; a missed detection is the only way #95425 returns.
"""
src_lib = _store_root(source_file) / "lib"
if not src_lib.is_dir():
return
dst_lib = venv_dir / "lib"
try:
dst_lib.mkdir(parents=True, exist_ok=True)
for src in src_lib.glob("libpython*"):
if not src.is_file():
continue
dst = dst_lib / src.name
if dst.exists() or dst.is_symlink():
if not refresh:
continue
try:
dst.unlink()
except OSError:
continue
try:
os.link(src, dst)
except OSError:
try:
shutil.copy2(src, dst)
except OSError:
logger.debug("libpython provision failed for %s", src, exc_info=True)
except OSError:
logger.debug("libpython provision skipped", exc_info=True)
def _copy_alias(venv_bin: Path, name: str, anchor: Path) -> None:
"""Materialize *name* as a real-file copy of *anchor* (atomic rename)."""
tmp = venv_bin / f".{name}.tcc-tmp"
try:
shutil.copy2(anchor, tmp)
os.chmod(tmp, anchor.stat().st_mode | 0o111)
os.replace(tmp, venv_bin / name)
except OSError:
try:
tmp.unlink(missing_ok=True)
except OSError:
pass
def _materialize_aliases(
venv_bin: Path, anchor: Path, *, refresh: bool = False
) -> None:
"""Materialize uv alias names as real-file copies of the anchor."""
names = set(_sibling_names())
try:
names.update(
p.name
for p in venv_bin.glob("python3*")
if re.fullmatch(r"python3(\.\d+)?", p.name)
)
except OSError:
pass
for name in sorted(names):
alias = venv_bin / name
try:
if refresh or alias.is_symlink() or not alias.exists():
_copy_alias(venv_bin, name, anchor)
except OSError:
continue
def _passes_boot_gate(staged: Path, venv_dir: Path) -> bool:
"""Launch *staged* and demand encodings + the venv prefix.
``OSError`` (fixture / wrong arch / exec-format) is treated as a skip —
we cannot verify, and we also cannot brick a host that cannot run the
binary. A real crash (dyld, encodings, wrong prefix) refuses the install.
"""
try:
proc = subprocess.run(
[str(staged), "-c", "import encodings, sys; print(sys.prefix)"],
capture_output=True,
text=True,
timeout=30,
)
except OSError:
return True
except subprocess.TimeoutExpired:
return False
if proc.returncode != 0:
return False
printed = (proc.stdout or "").strip().splitlines()
if not printed:
return False
try:
return Path(printed[-1]).resolve() == venv_dir.resolve()
except OSError:
return str(venv_dir) in printed[-1]
def _install_anchor(venv_dir: Path, source_file: Path) -> None:
"""Replace ``bin/python`` with a signed copy, gated on a real boot."""
venv_py = venv_python_path(venv_dir)
venv_bin = venv_py.parent
venv_bin.mkdir(parents=True, exist_ok=True)
_provision_libpython(venv_dir, source_file, refresh=True)
fd, tmp_name = tempfile.mkstemp(prefix=".python-tcc-", dir=str(venv_bin))
os.close(fd)
tmp_path = Path(tmp_name)
try:
shutil.copy2(source_file, tmp_path)
os.chmod(tmp_path, source_file.stat().st_mode | 0o111)
try:
from hermes_cli.managed_uv import _macos_sign_managed_python
_macos_sign_managed_python(tmp_path)
except Exception: # pragma: no cover - never block the anchor
logger.debug("anchor copy signing skipped", exc_info=True)
if not _passes_boot_gate(tmp_path, venv_dir):
raise _BootGateFailed(
f"staged copy at {tmp_path} failed encodings/prefix probe"
)
os.replace(tmp_path, venv_py)
_anchor_marker(venv_bin).write_text(str(source_file), encoding="utf-8")
_materialize_aliases(venv_bin, venv_py, refresh=True)
except Exception:
try:
tmp_path.unlink(missing_ok=True)
except OSError:
pass
raise
def ensure_tcc_anchor(project_root: Path | None = None) -> Path | None:
"""Pin a dylib-complete interpreter anchor for macOS TCC (#95596).
No-op (returns None) on non-macOS, when no venv interpreter exists, or
when the interpreter is not uv-managed. Idempotent. Best-effort —
returns None (and logs) if the copy or boot-gate fails; callers must
never depend on success.
"""
if not is_macos():
return None
venv_dir = _venv_dir(project_root)
if venv_dir is None:
return None
venv_py = venv_python_path(venv_dir)
if not (venv_py.is_file() or venv_py.is_symlink()):
return None
source = _interpreter_source(venv_dir)
if source is None or not _is_uv_macos_store(source):
return None
source_file = _interpreter_file(source)
if source_file is None:
return None
if not venv_py.is_symlink():
marker = _anchor_marker(venv_py.parent)
try:
if marker.is_file() and marker.read_text(encoding="utf-8").strip() == str(
source_file
):
_provision_libpython(venv_dir, source_file, refresh=False)
if _passes_boot_gate(venv_py, venv_dir):
_materialize_aliases(venv_py.parent, venv_py)
return venv_py
except OSError:
pass
try:
_install_anchor(venv_dir, source_file)
except _BootGateFailed as exc:
logger.warning("macOS TCC anchor boot-gate refused install: %s", exc)
return None
except Exception as exc: # best-effort: never break update/doctor
logger.warning("macOS TCC anchor install failed: %s", exc)
return None
return venv_py
def tcc_anchor_state(project_root: Path | None = None) -> tuple[str, str]:
"""Report the anchor state for ``hermes doctor``.
Returns ``(status, detail)`` with status one of:
- ``"skip"`` — not applicable (non-macOS, no venv, or not uv-managed)
- ``"active"`` — venv interpreter is pinned at a stable real-file anchor
- ``"stale"`` — pinned but the interpreter changed since the last copy
- ``"missing"`` — uv-managed interpreter with no stable anchor installed
"""
if not is_macos():
return "skip", "not macOS"
venv_dir = _venv_dir(project_root)
if venv_dir is None:
return "skip", "no venv interpreter"
venv_py = venv_python_path(venv_dir)
if not (venv_py.is_file() or venv_py.is_symlink()):
return "skip", "no venv interpreter"
source = _interpreter_source(venv_dir)
if source is None or not _is_uv_macos_store(source):
return "skip", "interpreter not uv-managed (stable path)"
if not venv_py.is_symlink():
marker = _anchor_marker(venv_py.parent)
source_file = _interpreter_file(source)
expected = str(source_file) if source_file is not None else source
try:
if marker.is_file() and marker.read_text(encoding="utf-8").strip() == expected:
return "active", str(venv_py)
except OSError:
pass
return "stale", str(venv_py)
return "missing", str(venv_py)

View File

@@ -8467,13 +8467,14 @@ def _cmd_update_impl(args, gateway_mode: bool):
"fully quit & relaunch once."
)
# NOTE: the macOS TCC interpreter anchor that used to refresh here
# (#95131/#95478) is REVERTED: the anchored real-file copy could not
# load libpython (LC_RPATH resolved into venv/lib/), bricking every
# hermes command on real Macs (#95425), and re-pointed aliases lost
# the stdlib (#95541). `hermes doctor` now heals already-anchored
# venvs back to symlinks. Re-land requires a dylib-complete design
# verified on macOS hardware first.
# macOS TCC interpreter anchor (#95596): dylib-complete re-land.
# Boot-gated — a failed probe leaves the venv untouched.
try:
from hermes_cli.macos_tcc_anchor import ensure_tcc_anchor
ensure_tcc_anchor()
except Exception:
logger.debug("macOS TCC anchor refresh skipped", exc_info=True)
# ── Post-update state.db integrity guard (#68474) ─────────────────
# Verify that state.db survived the update intact. If the live file

View File

@@ -0,0 +1,452 @@
"""Tests for the dylib-complete macOS TCC anchor (issue #95596).
Re-land of the interpreter anchor reverted in #95563. The first landing
bricked real Macs two ways: dynamically-linked builds died in dyld because
``@executable_path/../lib/libpython`` resolved into ``venv/lib/`` (#95425),
and alias symlinks to the copied interpreter lost the venv prefix (#95541).
Linux tests use fake checkout/uv-store layouts with ``platform.system``
monkeypatched. The real-interpreter E2E is ``macos_only`` so it runs on
the existing macOS CI job, not against one-byte fixtures.
"""
from __future__ import annotations
import os
import platform
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
import hermes_cli.doctor as doctor
import hermes_cli.macos_tcc_anchor as tcc
from hermes_constants import venv_python_path
def _darwin(monkeypatch):
monkeypatch.setattr(tcc.platform, "system", lambda: "Darwin")
def _linux(monkeypatch):
monkeypatch.setattr(tcc.platform, "system", lambda: "Linux")
def _build_store(tmp_path, version: str = "3.11.15", *, with_libpython: bool = False) -> Path:
store = (
tmp_path
/ "uv-store"
/ "uv"
/ "python"
/ f"cpython-{version}-macos-aarch64-none"
)
store_bin = store / "bin"
store_bin.mkdir(parents=True)
store_py = store_bin / "python3.11"
store_py.write_bytes(f"#!fake interpreter {version}".encode())
store_py.chmod(0o755)
if with_libpython:
lib = store / "lib"
lib.mkdir(parents=True)
(lib / "libpython3.11.dylib").write_bytes(b"fake dylib")
return store_bin
def _build_checkout(
tmp_path,
*,
store_bin: Path | None = None,
version: str = "3.11.15",
anchored: bool = False,
homebrew: bool = False,
with_libpython: bool = False,
) -> Path:
root = tmp_path / "checkout"
venv = root / ".venv"
venv_bin = venv / "bin"
venv_bin.mkdir(parents=True)
if homebrew:
brew = tmp_path / "opt" / "homebrew" / "bin"
brew.mkdir(parents=True)
brew_py = brew / "python3.14"
brew_py.write_bytes(b"#!homebrew")
brew_py.chmod(0o755)
(venv / "pyvenv.cfg").write_text(f"home = {brew}\n")
os.symlink(brew_py, venv_bin / "python")
os.symlink(brew_py, venv_bin / "python3")
return root
if store_bin is None:
store_bin = _build_store(tmp_path, version, with_libpython=with_libpython)
(venv / "pyvenv.cfg").write_text(f"home = {store_bin}\n")
store_py = store_bin / "python3.11"
if anchored:
venv_py = venv_bin / "python"
venv_py.write_bytes(store_py.read_bytes())
venv_py.chmod(0o755)
(venv_bin / ".tcc-anchor-source").write_text(str(store_py), encoding="utf-8")
os.symlink(venv_py, venv_bin / "python3")
else:
os.symlink(store_py, venv_bin / "python")
os.symlink(store_py, venv_bin / "python3")
return root
class TestUvStoreDetection:
def test_matches_uv_macos_store_path(self):
path = (
"/Users/u/.local/share/uv/python/"
"cpython-3.11.15-macos-aarch64-none/bin/python3.11"
)
assert tcc._is_uv_macos_store(path)
def test_matches_hermes_runtime_repair_generation(self):
path = (
"/Users/u/hermes-agent/.hermes-runtime/python/"
"generation-a1b2c3/cpython-3.11.15-macos-aarch64-none/bin/python3.11"
)
assert tcc._is_uv_macos_store(path)
def test_rejects_homebrew_interpreter(self):
path = (
"/opt/homebrew/Cellar/python@3.14/3.14.6/Frameworks/"
"Python.framework/Versions/3.14/bin/python3.14"
)
assert not tcc._is_uv_macos_store(path)
def test_rejects_linux_interpreter(self):
assert not tcc._is_uv_macos_store("/usr/bin/python3")
def test_rejects_uv_store_on_linux(self):
path = (
"/home/u/.local/share/uv/python/"
"cpython-3.11.15-x86_64-unknown-linux-gnu/bin/python3.11"
)
assert not tcc._is_uv_macos_store(path)
class TestEnsureTccAnchor:
def test_noop_on_non_macos(self, tmp_path, monkeypatch):
_linux(monkeypatch)
root = _build_checkout(tmp_path, store_bin=_build_store(tmp_path))
venv_py = venv_python_path(root / ".venv")
assert tcc.ensure_tcc_anchor(root) is None
assert venv_py.is_symlink()
def test_install_signs_the_anchor_copy(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
signed = []
import hermes_cli.managed_uv as managed_uv
monkeypatch.setattr(
managed_uv, "_macos_sign_managed_python", lambda p: signed.append(Path(p)) or True
)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin)
anchored = tcc.ensure_tcc_anchor(root)
assert anchored is not None
assert len(signed) == 1
assert signed[0].parent == anchored.parent
def test_anchors_repair_generation_interpreter(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store = (
tmp_path
/ "checkout"
/ ".hermes-runtime"
/ "python"
/ "generation-a1b2c3"
/ "cpython-3.11.15-macos-aarch64-none"
)
store_bin = store / "bin"
store_bin.mkdir(parents=True)
store_py = store_bin / "python3.11"
store_py.write_bytes(b"#!fake generation interpreter")
store_py.chmod(0o755)
root = _build_checkout(tmp_path, store_bin=store_bin)
venv_py = venv_python_path(root / ".venv")
assert venv_py.is_symlink()
anchored = tcc.ensure_tcc_anchor(root)
assert anchored == venv_py
assert not venv_py.is_symlink()
assert venv_py.read_bytes() == store_py.read_bytes()
def test_anchors_uv_managed_interpreter(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin)
venv_py = venv_python_path(root / ".venv")
assert venv_py.is_symlink()
anchored = tcc.ensure_tcc_anchor(root)
assert anchored == venv_py
assert venv_py.is_file() and not venv_py.is_symlink()
assert venv_py.read_bytes() == (store_bin / "python3.11").read_bytes()
assert os.access(venv_py, os.X_OK)
marker = venv_py.parent / ".tcc-anchor-source"
assert marker.read_text(encoding="utf-8").strip() == str(
store_bin / "python3.11"
)
alias = venv_py.parent / "python3"
assert alias.is_file() and not alias.is_symlink()
assert alias.read_bytes() == venv_py.read_bytes()
def test_idempotent(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin, anchored=True)
venv_py = venv_python_path(root / ".venv")
marker = venv_py.parent / ".tcc-anchor-source"
before = marker.read_text(encoding="utf-8")
anchored = tcc.ensure_tcc_anchor(root)
assert anchored == venv_py
assert venv_py.is_file() and not venv_py.is_symlink()
assert marker.read_text(encoding="utf-8") == before
def test_repairs_alias_symlinks_left_by_predecessor(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin, anchored=True)
venv_bin = root / ".venv" / "bin"
venv_py = venv_bin / "python"
assert (venv_bin / "python3").is_symlink()
anchored = tcc.ensure_tcc_anchor(root)
assert anchored == venv_py
alias = venv_bin / "python3"
assert alias.is_file() and not alias.is_symlink()
assert alias.read_bytes() == venv_py.read_bytes()
def test_reanchors_after_patch_bump(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
old_bin = _build_store(tmp_path, version="3.11.15")
root = _build_checkout(tmp_path, store_bin=old_bin, anchored=True)
venv_py = venv_python_path(root / ".venv")
new_bin = _build_store(tmp_path, version="3.11.16")
new_py = new_bin / "python3.11"
venv_py.unlink()
os.symlink(new_py, venv_py)
(root / ".venv" / "pyvenv.cfg").write_text(f"home = {new_bin}\n")
anchored = tcc.ensure_tcc_anchor(root)
assert anchored == venv_py
assert not venv_py.is_symlink()
assert venv_py.read_bytes() == new_py.read_bytes()
marker = venv_py.parent / ".tcc-anchor-source"
assert marker.read_text(encoding="utf-8").strip() == str(new_py)
alias = venv_py.parent / "python3"
assert alias.is_file() and not alias.is_symlink()
assert alias.read_bytes() == new_py.read_bytes()
def test_skips_homebrew_interpreter(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
root = _build_checkout(tmp_path, homebrew=True)
venv_py = venv_python_path(root / ".venv")
assert tcc.ensure_tcc_anchor(root) is None
assert venv_py.is_symlink()
def test_no_venv_returns_none(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
assert tcc.ensure_tcc_anchor(tmp_path / "missing") is None
def test_preserves_stdlib_source_home(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin)
cfg = root / ".venv" / "pyvenv.cfg"
tcc.ensure_tcc_anchor(root)
assert f"home = {store_bin}" in cfg.read_text(encoding="utf-8")
def test_provisions_libpython_as_hardlink_when_present(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path, with_libpython=True)
root = _build_checkout(tmp_path, store_bin=store_bin)
src_dylib = store_bin.parent / "lib" / "libpython3.11.dylib"
tcc.ensure_tcc_anchor(root)
dst = root / ".venv" / "lib" / "libpython3.11.dylib"
assert dst.is_file()
assert dst.read_bytes() == src_dylib.read_bytes()
assert dst.stat().st_ino == src_dylib.stat().st_ino
def test_boot_gate_refusal_leaves_venv_untouched(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin)
venv_py = venv_python_path(root / ".venv")
monkeypatch.setattr(tcc, "_passes_boot_gate", lambda *a, **k: False)
assert tcc.ensure_tcc_anchor(root) is None
assert venv_py.is_symlink()
assert not (venv_py.parent / ".tcc-anchor-source").exists()
class TestTccAnchorState:
def test_state_missing_then_active(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin)
status, detail = tcc.tcc_anchor_state(root)
assert status == "missing"
assert str(venv_python_path(root / ".venv")) in detail
tcc.ensure_tcc_anchor(root)
status, detail = tcc.tcc_anchor_state(root)
assert status == "active"
def test_state_skip_on_linux(self, tmp_path, monkeypatch):
_linux(monkeypatch)
store_bin = _build_store(tmp_path)
root = _build_checkout(tmp_path, store_bin=store_bin)
status, detail = tcc.tcc_anchor_state(root)
assert status == "skip"
assert detail == "not macOS"
def test_state_skip_for_homebrew(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
root = _build_checkout(tmp_path, homebrew=True)
status, detail = tcc.tcc_anchor_state(root)
assert status == "skip"
assert "not uv-managed" in detail
def test_state_stale_after_patch_bump(self, tmp_path, monkeypatch):
_darwin(monkeypatch)
old_bin = _build_store(tmp_path, version="3.11.15")
root = _build_checkout(tmp_path, store_bin=old_bin, anchored=True)
new_bin = _build_store(tmp_path, version="3.11.16")
(root / ".venv" / "pyvenv.cfg").write_text(f"home = {new_bin}\n")
status, _ = tcc.tcc_anchor_state(root)
assert status == "stale"
anchored = tcc.ensure_tcc_anchor(root)
assert anchored == venv_python_path(root / ".venv")
assert (root / ".venv" / "bin" / "python").read_bytes() == (
new_bin / "python3.11"
).read_bytes()
status, _ = tcc.tcc_anchor_state(root)
assert status == "active"
class TestDoctorCheck:
def test_missing_warns_without_fix(self, monkeypatch, capsys):
monkeypatch.setattr(
tcc, "tcc_anchor_state", lambda *a, **k: ("missing", "/x/.venv/bin/python")
)
doctor.check_macos_tcc_anchor(should_fix=False)
out = capsys.readouterr().out
assert "macOS TCC anchor missing" in out
def test_fix_installs_anchor(self, monkeypatch, capsys):
monkeypatch.setattr(
tcc, "tcc_anchor_state", lambda *a, **k: ("missing", "/x/.venv/bin/python")
)
monkeypatch.setattr(
tcc, "ensure_tcc_anchor", lambda *a, **k: Path("/x/.venv/bin/python")
)
doctor.check_macos_tcc_anchor(should_fix=True)
out = capsys.readouterr().out
assert "macOS TCC anchor installed" in out
def test_active_reports_ok(self, monkeypatch, capsys):
monkeypatch.setattr(
tcc, "tcc_anchor_state", lambda *a, **k: ("active", "/x/.venv/bin/python")
)
doctor.check_macos_tcc_anchor(should_fix=False)
out = capsys.readouterr().out
assert "macOS TCC anchor active" in out
def test_skip_is_silent_on_non_macos(self, monkeypatch, capsys):
monkeypatch.setattr(
tcc, "tcc_anchor_state", lambda *a, **k: ("skip", "not macOS")
)
doctor.check_macos_tcc_anchor(should_fix=False)
assert capsys.readouterr().out == ""
def test_never_crashes_on_exception(self, monkeypatch, capsys):
def boom(*a, **k):
raise RuntimeError("tccd down")
monkeypatch.setattr(tcc, "tcc_anchor_state", boom)
doctor.check_macos_tcc_anchor(should_fix=False)
out = capsys.readouterr().out
assert "macOS TCC anchor check failed" in out
@pytest.mark.macos_only
class TestAnchoredAliasesBootE2E:
"""Real-interpreter proof that the re-land stays bootable (#95596).
Copies the running interpreter's real base binary into a fake uv-store
layout (stdlib via a ``lib`` symlink) and actually executes every
entry point after anchoring. ``macos_only`` so Linux CI cannot
greenwash this with a fixture.
"""
def test_python_entry_points_boot_after_anchor(self, tmp_path):
minor = f"python3.{sys.version_info.minor}"
base = Path(sys.base_prefix)
real_py = base / "bin" / minor
if not real_py.is_file() or real_py.is_symlink():
resolved = real_py.resolve() if real_py.exists() else None
if resolved is None or not resolved.is_file():
pytest.skip(f"no real base interpreter binary at {real_py}")
real_py = resolved
if not (base / "lib" / minor / "os.py").is_file():
pytest.skip("base stdlib not in the expected lib layout")
store = (
tmp_path
/ "uv"
/ "python"
/ f"cpython-{platform.python_version()}-macos-aarch64-none"
)
store_bin = store / "bin"
store_bin.mkdir(parents=True)
shutil.copy2(real_py, store_bin / minor)
os.symlink(base / "lib", store / "lib")
root = tmp_path / "checkout"
venv = root / ".venv"
venv_bin = venv / "bin"
venv_bin.mkdir(parents=True)
(venv / "lib" / minor / "site-packages").mkdir(parents=True)
(venv / "pyvenv.cfg").write_text(
f"home = {store_bin}\nversion = {platform.python_version()}\n",
encoding="utf-8",
)
os.symlink(store_bin / minor, venv_bin / "python")
os.symlink("python", venv_bin / "python3")
os.symlink("python", venv_bin / minor)
anchored = tcc.ensure_tcc_anchor(root)
assert anchored is not None
for name in ("python", "python3", minor):
probe = subprocess.run(
[str(venv_bin / name), "-c",
"import encodings, sys; print(sys.prefix)"],
capture_output=True,
text=True,
timeout=120,
)
assert probe.returncode == 0, (
f"{name} failed to boot after anchoring:\n{probe.stderr}"
)
assert str(venv) in probe.stdout

View File

@@ -1,78 +0,0 @@
"""Tests for the TCC-anchor revert heal (#95425 / #95541).
The interpreter anchor replaced venv/bin/python with a real-file copy that
could not load libpython on real Macs, bricking the CLI. The anchor is
reverted; doctor's check_macos_tcc_anchor_removed() restores anchored venvs
to symlinks using the marker the anchor left behind.
"""
import contextlib
import io
import os
from pathlib import Path
import hermes_cli.doctor as doctor_mod
def _capture(fn):
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
fn()
return buf.getvalue()
def _build_anchored_checkout(tmp_path):
"""A checkout whose venv the anchor converted: real-file python + marker."""
root = tmp_path / "checkout"
store_bin = tmp_path / "store" / "cpython-3.12.1-macos" / "bin"
store_bin.mkdir(parents=True)
source = store_bin / "python3.12"
source.write_bytes(b"#!store interpreter")
source.chmod(0o755)
venv_bin = root / "venv" / "bin"
venv_bin.mkdir(parents=True)
venv_py = venv_bin / "python"
venv_py.write_bytes(b"#!anchored copy (broken on real macs)")
venv_py.chmod(0o755)
(venv_bin / ".tcc-anchor-source").write_text(str(source), encoding="utf-8")
os.symlink(venv_py, venv_bin / "python3")
return root, source, venv_py
def test_silent_on_non_macos(monkeypatch, tmp_path):
monkeypatch.setattr(doctor_mod.sys, "platform", "linux")
assert _capture(doctor_mod.check_macos_tcc_anchor_removed) == ""
def test_silent_when_never_anchored(monkeypatch, tmp_path):
monkeypatch.setattr(doctor_mod.sys, "platform", "darwin")
root = tmp_path / "checkout"
(root / "venv" / "bin").mkdir(parents=True)
monkeypatch.setattr(
doctor_mod, "__file__", str(root / "hermes_cli" / "doctor.py")
)
out = _capture(doctor_mod.check_macos_tcc_anchor_removed)
assert out == ""
def test_heals_anchored_venv(monkeypatch, tmp_path):
monkeypatch.setattr(doctor_mod.sys, "platform", "darwin")
root, source, venv_py = _build_anchored_checkout(tmp_path)
# Point the check's root resolution at the fixture checkout.
monkeypatch.setattr(
doctor_mod, "__file__", str(root / "hermes_cli" / "doctor.py")
)
out = _capture(doctor_mod.check_macos_tcc_anchor_removed)
assert "TCC anchor removed" in out
assert venv_py.is_symlink()
assert Path(os.readlink(venv_py)) == source
assert not (venv_py.parent / ".tcc-anchor-source").exists()
# Aliases restored to point at bin/python.
alias = venv_py.parent / "python3"
assert alias.is_symlink()
assert os.readlink(alias) == "python"