fix(prompt): repair backend probe import (get_environment never existed)

The system-prompt backend probe imported a nonexistent symbol —
`from tools.environments import get_environment` — which always raised
ImportError: cannot import name 'get_environment'. The exception is caught
and only drops the live backend description to a static fallback, so it is
cosmetic, but it broke the live OS/user/cwd probe for every non-local
backend (docker/singularity/modal/daytona/ssh).

The real factory is `_create_environment` in tools.terminal_tool. Build the
environment the same way the live terminal path does (select backend image,
assemble ssh/container config from _get_env_config()), then run the probe.

Note: this does NOT affect tool loading — tool selection runs each tool's
check_fn and never consults this probe. Regression from #52147 (2026-06-25).

Closes #53667 (probe import); the 'cronjob-only' tool-collapse symptom is
not reproducible — tool selection has no probe dependency and memory's
check_fn is unconditionally True.
This commit is contained in:
teknium1 2026-06-28 02:16:28 -07:00 committed by Teknium
parent b508d4296e
commit aa50c1ba5d
2 changed files with 94 additions and 3 deletions

View file

@ -926,8 +926,7 @@ def _probe_remote_backend(env_type: str) -> str | None:
try:
# Import locally: tools/ imports are heavy and only relevant when a
# non-local backend is actually configured.
from tools.terminal_tool import _get_env_config # type: ignore
from tools.environments import get_environment # type: ignore
from tools.terminal_tool import _create_environment, _get_env_config # type: ignore
except Exception as e:
logger.debug("Backend probe unavailable (import failed): %s", e)
_BACKEND_PROBE_CACHE[cache_key] = ""
@ -935,7 +934,59 @@ def _probe_remote_backend(env_type: str) -> str | None:
try:
config = _get_env_config()
env = get_environment(config)
# Build the environment the same way tools/terminal_tool.py does for a
# live command: select the backend image, then assemble ssh/container
# config from the env-derived dict. (There is no `get_environment`
# factory — the real entry point is `_create_environment`.)
if env_type == "docker":
image = config.get("docker_image", "")
elif env_type == "singularity":
image = config.get("singularity_image", "")
elif env_type == "modal":
image = config.get("modal_image", "")
elif env_type == "daytona":
image = config.get("daytona_image", "")
else:
image = ""
ssh_config = None
if env_type == "ssh":
ssh_config = {
"host": config.get("ssh_host", ""),
"user": config.get("ssh_user", ""),
"port": config.get("ssh_port", 22),
"key": config.get("ssh_key", ""),
"persistent": config.get("ssh_persistent", False),
}
container_config = None
if env_type in {"docker", "singularity", "modal", "daytona"}:
container_config = {
"container_cpu": config.get("container_cpu", 1),
"container_memory": config.get("container_memory", 5120),
"container_disk": config.get("container_disk", 51200),
"container_persistent": config.get("container_persistent", True),
"modal_mode": config.get("modal_mode", "auto"),
"docker_volumes": config.get("docker_volumes", []),
"docker_mount_cwd_to_workspace": config.get("docker_mount_cwd_to_workspace", False),
"docker_forward_env": config.get("docker_forward_env", []),
"docker_env": config.get("docker_env", {}),
"docker_run_as_host_user": config.get("docker_run_as_host_user", False),
"docker_extra_args": config.get("docker_extra_args", []),
"docker_persist_across_processes": config.get("docker_persist_across_processes", True),
"docker_orphan_reaper": config.get("docker_orphan_reaper", True),
}
env = _create_environment(
env_type=env_type,
image=image,
cwd=config.get("cwd", ""),
timeout=config.get("timeout", 180),
ssh_config=ssh_config,
container_config=container_config,
task_id="prompt-backend-probe",
host_cwd=config.get("host_cwd"),
)
# Single-line POSIX probe — works on any Unixy backend. Wrapped in
# `2>/dev/null` so a missing binary doesn't pollute the output.
probe_cmd = (

View file

@ -1219,6 +1219,46 @@ class TestEnvironmentHints:
assert "Linux 6.8.0" in result
assert "/workspace" in result
def test_probe_remote_backend_imports_real_factory(self, monkeypatch):
"""Regression for #53667: the probe imported a nonexistent
``get_environment`` from ``tools.environments`` and always died with
``ImportError: cannot import name 'get_environment'`` (cosmetic it
only dropped the live backend description to a static fallback). The
real factory is ``_create_environment`` in ``tools.terminal_tool``;
the probe must import and call THAT, returning a parsed line instead
of None."""
import agent.prompt_builder as _pb
monkeypatch.setenv("TERMINAL_ENV", "docker")
_pb._clear_backend_probe_cache()
class _FakeEnv:
def execute(self, cmd, timeout=None):
return {
"returncode": 0,
"output": (
"os=Linux\nkernel=6.8.0\nhome=/root\n"
"cwd=/workspace\nuser=root\n"
),
}
created = {}
def _fake_create_environment(*, env_type, **kwargs):
created["env_type"] = env_type
return _FakeEnv()
# Patch the REAL factory in tools.terminal_tool — the probe imports it
# locally, so the import itself must succeed (the bug was here).
import tools.terminal_tool as _tt
monkeypatch.setattr(_tt, "_create_environment", _fake_create_environment)
line = _pb._probe_remote_backend("docker")
assert created.get("env_type") == "docker"
assert line is not None
assert "Linux 6.8.0" in line
assert "root" in line
def test_remote_backend_list_covers_known_sandboxes(self):
"""Regression guard: if someone adds a remote backend, they must list it here."""
import agent.prompt_builder as _pb