fix(cli): auto-restore a deleted ui-tui workspace from git before TUI launch

The Windows update path can leave tracked ui-tui/ files deleted in the
working tree (HEAD intact). The guard now self-heals: when ui-tui/ is
missing in a git checkout, run `git restore -- ui-tui` and continue,
falling back to the printed manual-recovery steps only when git can't
recover it (no checkout / restore failed).

Builds on konsisumer's missing-workspace guard.
This commit is contained in:
teknium1 2026-06-21 12:32:43 -07:00 committed by Teknium
parent 537ad9ea9a
commit db097fb088
2 changed files with 94 additions and 7 deletions

View file

@ -1650,8 +1650,50 @@ def _find_bundled_tui(hermes_cli_dir: Path | None = None) -> Path | None:
return bundled if bundled.is_file() else None
def _exit_missing_tui_workspace(tui_dir: Path) -> "NoReturn":
"""Abort TUI launch with a recovery hint when the workspace checkout is missing."""
def _restore_tui_workspace(tui_dir: Path) -> bool:
"""Try to restore a missing ``ui-tui/`` from git, returning True on success.
On Windows an antivirus / NTFS filter driver can leave tracked ``ui-tui/``
files deleted in the working tree after ``hermes update`` (HEAD stays
intact; the files just vanish see issue #49145). Those files are tracked,
so ``git restore`` puts them back deterministically. Best-effort: returns
False (rather than raising) when git is unavailable, this isn't a checkout,
or the restore leaves the directory still missing the caller then prints
the manual-recovery message.
"""
git = shutil.which("git")
if not git or not (tui_dir.parent / ".git").exists():
return False
try:
subprocess.run(
[git, "restore", "--", tui_dir.name],
cwd=str(tui_dir.parent),
capture_output=True,
text=True,
check=False,
)
except OSError:
return False
return tui_dir.is_dir()
def _ensure_tui_workspace(tui_dir: Path) -> None:
"""Ensure ``ui-tui/`` exists before any npm/node subprocess uses it as cwd.
Without this, a missing workspace falls through to ``subprocess.run(...,
cwd=<missing ui-tui>)``, which crashes with ``NotADirectoryError``
(``WinError 267`` on Windows) instead of a usable message (#49145). We
first try to self-heal via ``git restore``; only if that can't recover the
directory do we abort with concrete manual-recovery steps.
"""
if tui_dir.is_dir():
return
if _restore_tui_workspace(tui_dir):
if not os.environ.get("HERMES_QUIET"):
print(f"Restored missing TUI workspace: {tui_dir}")
return
print(
"Error: the TUI workspace is missing from this Hermes checkout.\n"
f"Expected directory: {tui_dir}\n"
@ -1699,8 +1741,8 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
)
sys.exit(1)
if not ext_dir and not tui_dir.is_dir():
_exit_missing_tui_workspace(tui_dir)
if not ext_dir:
_ensure_tui_workspace(tui_dir)
# 1. Prebuilt bundle (nix / packaged release): just run it.
if not tui_dev: