diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 4cc8d9df589..57bed0c9e26 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -2470,6 +2470,32 @@ def _make_xai_callback_handler(expected_path: str) -> tuple[type[BaseHTTPRequest "error_description": params.get("error_description", [None])[0], } + # Diagnostic logging — emits at INFO so reporters of loopback bugs + # (#27385 — "callback received but Hermes times out") can produce + # actionable evidence without a code change. Logged values are + # fingerprints / booleans only; no actual code/state strings leak + # into the log file. Run with ``HERMES_LOG_LEVEL=INFO`` (or check + # ``~/.hermes/logs/agent.log`` which captures INFO+ unconditionally). + try: + logger.info( + "xAI loopback callback received: path=%s has_code=%s has_state=%s has_error=%s " + "ua=%s", + parsed.path, + incoming["code"] is not None, + incoming["state"] is not None, + incoming["error"] is not None, + (self.headers.get("User-Agent") or "")[:80], + ) + if incoming["error"]: + logger.info( + "xAI loopback callback carries error=%s error_description=%s", + incoming["error"], + (incoming["error_description"] or "")[:200], + ) + except Exception: + # Logging must never break the OAuth flow. + pass + # Treat a hit on the callback path with neither `code` nor `error` # as a missing OAuth callback (e.g. xAI's auth backend failed to # redirect and the user navigated to the bare loopback URL by hand). @@ -2574,6 +2600,17 @@ def _xai_wait_for_callback( server.shutdown() server.server_close() thread.join(timeout=1.0) + # Diagnostic: distinguish "no callback ever arrived" from "callback + # arrived but result wasn't populated" (#27385). The per-hit handler + # also logs at INFO; if neither line appears, xAI's IDP never reached + # the loopback at all (firewall, port-binding, IPv6/IPv4 mismatch). + logger.info( + "xAI loopback wait timed out after %.0fs with no usable callback " + "(result.code=%s result.error=%s)", + max(5.0, timeout_seconds), + result["code"] is not None, + result["error"] is not None, + ) raise AuthError( "xAI authorization timed out waiting for the local callback.", provider="xai-oauth",