fix(terminal): guard os.getcwd() against a deleted CWD

`os.getcwd()` raises FileNotFoundError when the process's working
directory was removed out from under it (e.g. a scratch workspace
cleaned up mid-session), crashing terminal env setup.

Extract a `_safe_getcwd()` helper that falls back to TERMINAL_CWD, then
the user's home, on FileNotFoundError, and route all three `os.getcwd()`
call sites in terminal_tool.py through it (local default_cwd, the Docker
cwd-passthrough source, and the debug-config print) so the same crash
can't resurface at a sibling site. Adds unit tests for the real-cwd path
and both fallback branches.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
This commit is contained in:
Baris Sencan
2026-06-04 23:39:34 -07:00
committed by Teknium
co-authored by Teknium
parent b1e399de95
commit ad69d3edc7
2 changed files with 41 additions and 3 deletions
+17 -3
View File
@@ -1030,6 +1030,20 @@ def _parse_env_var(name: str, default: str, converter=int, type_label: str = "in
)
def _safe_getcwd() -> str:
"""Return the current working directory, tolerating a deleted CWD.
``os.getcwd()`` raises FileNotFoundError when the process's working
directory has been removed out from under it (e.g. a scratch workspace
that was cleaned up mid-session). Fall back to TERMINAL_CWD, then the
user's home directory, so terminal setup never crashes on a stale CWD.
"""
try:
return os.getcwd()
except FileNotFoundError:
return os.getenv("TERMINAL_CWD") or os.path.expanduser("~")
def _get_env_config() -> Dict[str, Any]:
"""Get terminal environment configuration from environment variables."""
# Default image with Python and Node.js for maximum compatibility
@@ -1042,7 +1056,7 @@ def _get_env_config() -> Dict[str, Any]:
# remote home, and everything else starts in the backend's default
# root-like cwd.
if env_type == "local":
default_cwd = os.getcwd()
default_cwd = _safe_getcwd()
elif env_type == "ssh":
default_cwd = "~"
else:
@@ -1058,7 +1072,7 @@ def _get_env_config() -> Dict[str, Any]:
host_cwd = None
host_prefixes = ("/Users/", "/home/", "C:\\", "C:/")
if env_type == "docker" and mount_docker_cwd:
docker_cwd_source = os.getenv("TERMINAL_CWD") or os.getcwd()
docker_cwd_source = os.getenv("TERMINAL_CWD") or _safe_getcwd()
candidate = os.path.abspath(os.path.expanduser(docker_cwd_source))
if (
any(candidate.startswith(p) for p in host_prefixes)
@@ -2516,7 +2530,7 @@ if __name__ == "__main__":
print(f" TERMINAL_SINGULARITY_IMAGE: {os.getenv('TERMINAL_SINGULARITY_IMAGE', f'docker://{default_img}')}")
print(f" TERMINAL_MODAL_IMAGE: {os.getenv('TERMINAL_MODAL_IMAGE', default_img)}")
print(f" TERMINAL_DAYTONA_IMAGE: {os.getenv('TERMINAL_DAYTONA_IMAGE', default_img)}")
print(f" TERMINAL_CWD: {os.getenv('TERMINAL_CWD', os.getcwd())}")
print(f" TERMINAL_CWD: {os.getenv('TERMINAL_CWD', _safe_getcwd())}")
from hermes_constants import display_hermes_home as _dhh
print(f" TERMINAL_SANDBOX_DIR: {os.getenv('TERMINAL_SANDBOX_DIR', f'{_dhh()}/sandboxes')}")
print(f" TERMINAL_TIMEOUT: {os.getenv('TERMINAL_TIMEOUT', '60')}")