feat(oauth): add --manual-paste fallback for browser-only remote consoles (closes #26923)#28358
Merged
Conversation
xAI Grok OAuth (and Spotify) use a loopback redirect to ``http://127.0.0.1:<port>/callback`` to capture the authorization code. That works when the browser and Hermes run on the same machine, and the SSH tunnel recipe handles the regular remote case. It breaks completely on **browser-only remote consoles** (GCP Cloud Shell, GitHub Codespaces, AWS EC2 Instance Connect, Gitpod, Replit, …) where the user has a browser but no real SSH client to forward a port — the redirect to 127.0.0.1 on the remote VM simply isn't reachable from the laptop, and there's nothing the existing flow can do about it (#26923). This commit adds the foundation for a manual-paste fallback: * ``_is_remote_session`` now also recognises Cloud Shell, Codespaces, Gitpod, Replit, StackBlitz (in addition to SSH), so the existing tunnel hint at least fires in those environments. * ``_parse_pasted_callback`` accepts any of: a full ``http(s)://...?code=...&state=...`` URL, a bare ``?code=...`` query string, a bare ``code=...&state=...`` fragment, or a bare opaque code value. Returns the same dict shape the HTTP callback handler produces, so the caller's state / error validation works unchanged (no CSRF bypass). * ``_prompt_manual_callback_paste`` reads stdin with a clear multi-line explanation of what's happening and what to paste. * ``_xai_oauth_loopback_login`` gains a ``manual_paste`` kwarg that skips the HTTP listener entirely. The redirect_uri, PKCE verifier, state, and nonce are byte-identical to the loopback path so xAI's token endpoint can't tell the difference at the protocol level. * ``_print_loopback_ssh_hint`` now also mentions ``--manual-paste`` so users without a real SSH client see a path forward instead of a dead-end tunnel recipe. * ``_login_xai_oauth`` threads ``args.manual_paste`` into the loopback helper.
…model`` Register the new ``--manual-paste`` flag on both entry points and thread it through to the xAI loopback login: * ``hermes auth add xai-oauth --manual-paste`` — pool-add path, forwarded inside ``auth_commands.handle_auth_add``. * ``hermes model --manual-paste`` — model-picker path, forwarded by ``_model_flow_xai_oauth`` into the synthetic ``argparse.Namespace`` it passes to ``_login_xai_oauth``. The picker also now forwards ``--no-browser`` and ``--timeout`` for consistency (previously hardcoded to defaults regardless of CLI flags). Help text on both flags points at #26923 and names the browser-only remote consoles (Cloud Shell, Codespaces, EC2 Instance Connect) so users searching ``hermes --help`` can find the workaround.
…y path (#26923) Tests (``tests/hermes_cli/test_auth_manual_paste.py``): * 9 parametrised + scalar cases for ``_is_remote_session`` covering the new Cloud Shell / Codespaces / Gitpod / Replit / StackBlitz env vars (plus the existing SSH ones). * 9 cases for ``_parse_pasted_callback`` covering every paste form (full URL, https URL with extra params, bare ``?code=...``, bare ``code=...`` fragment, bare opaque value, error+description, empty, whitespace-only, malformed URL). * 3 cases for ``_prompt_manual_callback_paste`` (happy path, EOF, Ctrl-C). * 3 end-to-end ``_xai_oauth_loopback_login(manual_paste=True)`` cases: the HTTP server MUST NOT be started (asserted via a callable that raises if invoked), wrong state still rejected with ``xai_state_mismatch`` (no CSRF bypass), and empty paste surfaces ``xai_code_missing``. * SSH-hint mention test ensures the ``--manual-paste`` instruction is printed in the remote-session hint. Docs: * ``oauth-over-ssh.md`` — new "Browser-only remote (Cloud Shell / Codespaces / EC2 Instance Connect)" section with the ``--manual-paste`` recipe, plus a TL;DR note for the new flag. * ``xai-grok-oauth.md`` — short subsection pointing at the same recipe and the OAuth-over-SSH guide anchor.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
First entries
tests/hermes_cli/test_auth_manual_paste.py:30: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
✅ Fixed issues: none
Unchanged: 4626 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Salvage of #26929 (@xxxigm).
Summary
Adds a
--manual-pastefallback tohermes auth add xai-oauthandhermes modelso users on browser-only remote consoles — GCP Cloud Shell, GitHub Codespaces, AWS Instance Connect, Gitpod, StackBlitz, Replit — can complete xAI Grok OAuth login without an SSH tunnel. Also unblocks WSL2 users hit by the Hyper-V Firewall default-block on the loopback callback.Changes
hermes_cli/auth.py:_xai_oauth_loopback_login(manual_paste=True)skips the HTTP listener entirely. Redirect URI, PKCE verifier, state, and nonce are byte-identical to the loopback path — purely a transport change for the callback hop, not a security downgrade._parse_pasted_callbackand_prompt_manual_callback_paste(designed to be reused by Spotify in a follow-up)._is_remote_session()broadened to recognizeCLOUD_SHELL,CODESPACES,CODESPACE_NAME,GITPOD_WORKSPACE_ID,REPL_ID,STACKBLITZin addition toSSH_CLIENT/SSH_TTY. Existing tunnel hint also mentions--manual-paste.hermes_cli/auth_commands.py+hermes_cli/main.py: register--manual-pasteon bothhermes auth addandhermes model.hermes modelnow also forwards--no-browserand--timeout.tests/hermes_cli/test_auth_manual_paste.py(new): pins manual-paste semantics.website/docs/guides/oauth-over-ssh.md+website/docs/guides/xai-grok-oauth.md: documents the browser-only console path.Validation
scripts/run_tests.sh tests/hermes_cli/test_auth_manual_paste.py tests/hermes_cli/test_auth_xai_oauth_provider.py tests/hermes_cli/test_auth_commands.py tests/hermes_cli/test_auth_loopback_ssh_hint.py -q→ 157/157 passing.scripts/run_tests.sh tests/hermes_cli/test_argparse_flag_propagation.py -q→ 14/14 passing.Authorship preserved via cherry-pick across 3 commits.
Note for contributor: this also closes the same gap addressed by #27523 (@levi951) and #27556 (@WinterArc21) — both closed in favor of this PR which was first-submitted and broader in scope (remote-env detection broadening + tests + docs).