CLI 자식 프로세스 환경에 Windows 필수 변수 백필

공개 런타임 승격 체인이 psutil로 이전 프로세스 환경을 통째로 이식하는 과정에서
SystemRoot 가 유실됐고, Go 계열 CLI(agy)는 시스템 인증서 풀/홈 해석에 SystemRoot 가
필요해 agy models 가 조용히 빈 목록을 반환했다(관리자 AI 운영 화면의
'Agy가 선택 가능한 모델을 반환하지 않았습니다' 두 번째 원인).
- _cli_subprocess_env(): 상속 환경에서 빠진 SystemRoot/SystemDrive/ComSpec 만
  기본값으로 백필해 모든 CLI 스폰(_run_process·codex app-server·agy stream·claude 세션)에 적용.
  os.environ 의 Windows 대문자 정규화를 고려한 대소문자 무시 조회
- Set-CompleteProcessEnvironment: 이식본에 빠진 Windows 필수 키를 Machine 스코프
  표준값으로 병합해 승격 체인 자체의 유실을 원천 보강
- 검증: 신규 2단위 RED→GREEN, engine_gateway 65 passed, app 924 passed,
  PS 5.1 parser OK, SystemRoot 제거 환경에서 실제 agy CLI 14모델 live 조회 확인
This commit is contained in:
Yun Chan 2026-08-18 13:25:08 +09:00
parent 5afd92f8f3
commit 6369f29439
4 changed files with 73 additions and 0 deletions

View file

@ -128,6 +128,33 @@ def _safe_process_error(stderr: bytes, fallback: str) -> str:
return detail[-1200:]
# Windows 필수 변수의 표준 기본값 — 승격 체인의 psutil 환경 이식에서 유실될 수 있다.
_WINDOWS_ESSENTIAL_ENV_DEFAULTS = {
"SystemRoot": r"C:\Windows",
"SystemDrive": "C:",
"ComSpec": r"C:\Windows\system32\cmd.exe",
}
def _cli_subprocess_env() -> dict[str, str]:
"""CLI 자식 프로세스에 물려줄 환경.
공개 런타임 승격은 이전 프로세스 환경을 psutil로 통째로 이식하는데, 캡처에서
SystemRoot 같은 Windows 필수 변수가 유실되면 Go 계열 CLI(agy) 시스템 인증서
· 디렉터리 해석에 실패하고 모델 목록을 조용히 내놓는다(2026-08-18 실측).
상속 환경에서 빠진 필수 키만 기본값으로 채운다.
"""
env = dict(os.environ)
if os.name == "nt":
# os.environ은 Windows에서 키를 대문자로 정규화하므로 대소문자 무시 조회한다.
upper_names = {key.upper(): key for key in env}
for name, default in _WINDOWS_ESSENTIAL_ENV_DEFAULTS.items():
existing = upper_names.get(name.upper())
if existing is None or not env[existing]:
env[name] = default
return env
async def _run_process(
args: list[str],
*,
@ -141,6 +168,7 @@ async def _run_process(
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=cwd,
env=_cli_subprocess_env(),
)
try:
stdout, stderr = await asyncio.wait_for(
@ -233,6 +261,7 @@ async def _codex_model_list(binary: str) -> dict[str, Any]:
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=_cli_subprocess_env(),
)
if proc.stdin is None or proc.stdout is None:
proc.kill()
@ -681,6 +710,7 @@ async def _stream_agy(
cwd=str(_cli_runtime_cwd()),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=_cli_subprocess_env(),
)
assert proc.stdout is not None
assert proc.stderr is not None