mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-08 08:11:38 +00:00
fix(xai-oauth): show "not received" page when loopback callback has no code
When xAI's auth backend fails to redirect (e.g. the German "We couldn't reach your app" fallback shown in #27385), users sometimes navigate manually to the bare loopback callback URL — `http://127.0.0.1:<port>/callback` with no query string. The handler used to return 200 "xAI authorization received" for any GET that hit the expected path, because `parse_qs("")` yields no `code` and no `error`, leaving `result` untouched while the success page was still served. The CLI's wait loop, of course, still saw no code and timed out with `AuthError: xAI authorization timed out waiting for the local callback.` The user is left looking at a browser tab that claims success and a terminal that says failure — exactly the contradiction in #27385. This change makes the empty-callback case return 400 with an explicit "not received" page and a hint to retry `hermes auth add xai-oauth`. The wait-loop semantics are unchanged: `result["code"]` and `result["error"]` both stay None, so the CLI still raises a real timeout rather than treating the bare hit as a successful callback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1fabd6e100
commit
bf6eeb3f93
2 changed files with 104 additions and 4 deletions
|
|
@ -2405,15 +2405,37 @@ def _make_xai_callback_handler(expected_path: str) -> tuple[type[BaseHTTPRequest
|
|||
"error": params.get("error", [None])[0],
|
||||
"error_description": params.get("error_description", [None])[0],
|
||||
}
|
||||
|
||||
# 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).
|
||||
# Show an explicit "not received" page rather than the success page —
|
||||
# otherwise the browser claims authorization succeeded while the CLI
|
||||
# is still waiting for a real callback and eventually times out.
|
||||
if incoming["code"] is None and incoming["error"] is None:
|
||||
self.send_response(400)
|
||||
self._maybe_write_cors_headers()
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.end_headers()
|
||||
body = (
|
||||
"<html><body>"
|
||||
"<h1>xAI authorization not received.</h1>"
|
||||
"<p>No authorization code was present in this callback URL. "
|
||||
"Return to the terminal and re-run "
|
||||
"<code>hermes auth add xai-oauth</code> to retry.</p>"
|
||||
"</body></html>"
|
||||
)
|
||||
self.wfile.write(body.encode("utf-8"))
|
||||
return
|
||||
|
||||
# ThreadingHTTPServer allows a fallback/manual callback to complete
|
||||
# while a browser connection is stuck. Once we have a terminal
|
||||
# OAuth result (code or error), keep the first one so a later
|
||||
# concurrent/invalid callback cannot overwrite state before
|
||||
# validation in _xai_oauth_loopback_login().
|
||||
if incoming["code"] or incoming["error"]:
|
||||
with result_lock:
|
||||
if not (result["code"] or result["error"]):
|
||||
result.update(incoming)
|
||||
with result_lock:
|
||||
if not (result["code"] or result["error"]):
|
||||
result.update(incoming)
|
||||
|
||||
self.send_response(200)
|
||||
self._maybe_write_cors_headers()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue