mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
[F091] warn at spawn time when host grok auth.json is missing
GrokCliProvider._append_grok_auth_mount silently skipped the mount when the host ~/.grok/auth.json was absent. The spawn still succeeded (docker run returned 0 — the container was created), so the operator had no spawn-time signal that the agent was doomed: the entrypoint's `python -m roboco.llm.providers.grok_auth --check` backstop then refused to start (exit 78) and the failure only surfaced later via the container's log markers. Fix: emit a spawn-time WARNING (module logger) naming the missing file and the remediation (`grok login` on the host, or set ROBOCO_HOST_GROK_DIR) when the mount is skipped. The spawn outcome is unchanged — the container still starts and the existing exit-78 -> park flow (F041) still catches it — but the operator now sees the missing credential immediately instead of diagnosing a later exit-78. Logical-regression check: the mount-present path is byte-for-byte unchanged (auth.json exists -> the -v bind is appended, no warning); the spawn still succeeds when auth is absent (no raise — the existing test_grok_spawn_omits_auth_mount_when_absent still passes: no mount, no crash); the exit-78 entrypoint backstop and the orchestrator's exit-78-park handling (F041) are untouched; a module-level logger adds no side effects. Tests: new test_grok_spawn_warns_when_auth_absent uses caplog to assert a WARNING mentioning auth.json + `grok login` is emitted on a missing-credential spawn (RED before: no warning; GREEN after). 102 grok tests green; ruff/mypy clean.
This commit is contained in:
@@ -27,6 +27,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Protocol
|
||||
@@ -37,6 +38,8 @@ from roboco.llm.providers.base import AgentProvider, ProviderError, SpawnResult
|
||||
if TYPE_CHECKING:
|
||||
from roboco.models.runtime import OrchestratorAgentConfig as AgentConfig
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
# The Grok agent image (own image, like every other agent role). Overridable for
|
||||
# tests / staged rollout.
|
||||
_DEFAULT_GROK_IMAGE = os.environ.get(
|
||||
@@ -179,6 +182,19 @@ class GrokCliProvider(AgentProvider):
|
||||
auth_dir = Path(GROK_AUTH_HOST_PATH)
|
||||
if (auth_dir / "auth.json").exists():
|
||||
cmd.extend(["-v", f"{auth_dir}:{_GROK_AUTH_DIR_IN_CONTAINER}:ro"])
|
||||
else:
|
||||
# The mount is the grok subscription credential — without it the
|
||||
# container starts but the entrypoint ``--check`` backstop refuses
|
||||
# to run (exit 78) and the agent is doomed. Fail loud at spawn time
|
||||
# so the operator sees the missing credential immediately instead
|
||||
# of diagnosing a later exit-78 from the container log markers.
|
||||
_log.warning(
|
||||
"grok host auth.json not found at %s — spawn will start the "
|
||||
"container but it is doomed to exit 78 (no SuperGrok credential). "
|
||||
"Run `grok login` on the host (or set ROBOCO_HOST_GROK_DIR to the "
|
||||
"directory holding auth.json) before spawning Grok agents.",
|
||||
auth_dir / "auth.json",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _append_usage_mount(cmd: list[str], hosts: dict[str, str | None]) -> None:
|
||||
|
||||
@@ -262,6 +262,26 @@ async def test_grok_spawn_omits_auth_mount_when_absent() -> None:
|
||||
assert not any("/home/agent/.grok-auth-ro" in c for c in cmd)
|
||||
|
||||
|
||||
async def test_grok_spawn_warns_when_auth_absent(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""A missing host auth.json must not be silent — the spawn is doomed to
|
||||
exit 78, so the operator gets a spawn-time WARNING naming the missing file
|
||||
and the remediation (``grok login`` on the host). Without it the container
|
||||
silently started and only failed later at the entrypoint ``--check``.
|
||||
"""
|
||||
caplog.set_level("WARNING", logger="roboco.llm.providers.grok")
|
||||
host = _FakeHost()
|
||||
provider = GrokCliProvider(host)
|
||||
with patch("asyncio.create_subprocess_exec", AsyncMock(return_value=_proc())):
|
||||
await provider.spawn(_config())
|
||||
warnings = [r for r in caplog.records if r.levelname == "WARNING"]
|
||||
assert warnings, "expected a spawn-time WARNING for the missing host auth.json"
|
||||
msg = warnings[0].getMessage()
|
||||
assert "auth.json" in msg
|
||||
assert "grok login" in msg # names the remediation
|
||||
|
||||
|
||||
async def test_grok_spawn_prompt_is_injection_safe() -> None:
|
||||
host = _FakeHost()
|
||||
provider = GrokCliProvider(host)
|
||||
|
||||
Reference in New Issue
Block a user