feat(oauth): add --manual-paste fallback for browser-only remote consoles (closes #26923)#26929
Closed
xxxigm wants to merge 3 commits into
Closed
feat(oauth): add --manual-paste fallback for browser-only remote consoles (closes #26923)#26929xxxigm wants to merge 3 commits into
xxxigm wants to merge 3 commits into
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 (NousResearch#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 NousResearch#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 (NousResearch#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.
This comment was marked as spam.
This comment was marked as spam.
|
Good job! |
This was referenced May 19, 2026
Contributor
|
Merged via PR #28358 — cherry-picked your three commits onto current main with authorship preserved (rebase-merge). 157/157 auth + manual-paste tests passing, 14/14 argparse propagation tests passing. Thanks for the broad remote-env detection (Cloud Shell / Codespaces / Gitpod / Replit / StackBlitz) — that's the right shape and is what unblocks the WSL2 firewall users too. |
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.
What does this PR do?
Adds a
--manual-pastefallback tohermes auth addandhermes modelfor the xAI Grok OAuth loopback flow, so users on browser-only remote consoles can complete OAuth login without a real SSH client.Issue #26923 reported that
hermes model(andhermes auth add xai-oauth) hangs forever on GCP Cloud Shell, GitHub Codespaces, AWS EC2 Instance Connect, Gitpod, etc., because the loopback callbackhttp://127.0.0.1:56121/callbackis bound on the remote VM and the user's laptop browser can't reach it. The existing SSH-tunnel hint only fires whenSSH_CLIENT/SSH_TTYare set — which Cloud Shell and friends don't set — leaving these users with literally no path forward.The reporter confirmed the workaround that works: after approving in their laptop's browser, manually
curlthe failed callback URL (with?code=...&state=...) at the loopback listener on the VM. They asked: would the maintainers be open to a PR that formalizes this manual code paste flow for all OAuth providers?This PR is that formalization, scoped to xAI Grok OAuth (the documented reproduction in the issue). The same helpers —
_parse_pasted_callback,_prompt_manual_callback_paste— are designed to be reused by Spotify (the only other true loopback flow inhermes_cli/auth.py) in a follow-up; the rest of the OAuth providers in this codebase use device-code flows that aren't affected.Three pieces:
_is_remote_session()now recognisesCLOUD_SHELL,CODESPACES,CODESPACE_NAME,GITPOD_WORKSPACE_ID,REPL_ID,STACKBLITZin addition toSSH_CLIENT/SSH_TTY. The existing tunnel hint at least fires in those environments — and the hint now also mentions--manual-pasteso users without a real SSH client see a path forward instead of a dead-end recipe._xai_oauth_loopback_login(manual_paste=True)skips the HTTP listener entirely. The redirect URI, PKCE verifier, state, and nonce are byte-identical to the loopback path so the upstream OAuth flow at xAI is exactly the same — this is purely a transport change for the callback hop, not a security downgrade.--manual-pasteis registered on bothhermes auth addandhermes model, forwarded through_login_xai_oauthand_model_flow_xai_oauth. The model picker now also forwards--no-browserand--timeout(previously hardcoded regardless of CLI flags) for consistency.The
_parse_pasted_callbackhelper accepts any of: a full URL (http(s)://127.0.0.1:56121/callback?code=...&state=...), a bare query string (?code=...&state=...orcode=...&state=...), or a bare opaque code value — whichever shape the user finds easiest to copy. The CSRFstatecheck still runs against the parsed result, so a wrong-state paste is rejected withxai_state_mismatch(no CSRF bypass).Related Issue
Fixes #26923
Type of Change
Changes Made
hermes_cli/auth.py— Broaden_is_remote_session()to recognise Cloud Shell / Codespaces / Gitpod / Replit / StackBlitz env vars. New_parse_pasted_callback(raw)and_prompt_manual_callback_paste(redirect_uri)helpers._xai_oauth_loopback_logingains amanual_paste: bool = Falsekwarg that skips the HTTP listener (still validating state / error / code through the existing branches, so no CSRF bypass)._print_loopback_ssh_hintnow also points users at--manual-paste._login_xai_oauthforwardsargs.manual_paste.hermes_cli/auth_commands.py— Pool-add path forwardsargs.manual_pasteto_xai_oauth_loopback_login(one-line wiring).hermes_cli/main.py— Register--manual-pasteon bothauth addandmodelparsers (help text names OAuth loopback login broken for remote/browser-based consoles (GCP, Codespaces, etc.) #26923 + the affected consoles)._model_flow_xai_oauthaccepts and forwardsargsso--manual-paste/--no-browser/--timeoutreach the inner login call.tests/hermes_cli/test_auth_manual_paste.py— 24 new tests in 5 sections:_is_remote_sessioncovering every documented remote env var (and a negative case when none are set)._parse_pasted_callbackcovering every paste form, including malformed-URL safety._prompt_manual_callback_paste(happy path, EOF, Ctrl-C)._xai_oauth_loopback_login(manual_paste=True): (a) the HTTP server must not be started (asserted via a callable that raises if invoked), (b) wrong state is still rejected withxai_state_mismatch, (c) empty paste surfacesxai_code_missing._print_loopback_ssh_hintnow mentions--manual-pastefor non-SSH remotes.website/docs/guides/oauth-over-ssh.md— TL;DR note for--manual-paste, plus new "Browser-only remote (Cloud Shell / Codespaces / EC2 Instance Connect)" section with the recipe.website/docs/guides/xai-grok-oauth.md— short subsection pointing at the same recipe.How to Test
For end-to-end verification on a real Cloud Shell / Codespaces session:
Checklist
Code
fix(scope):,feat(scope):,test+docs(scope):)Documentation & Housekeeping
website/docs/guides/oauth-over-ssh.md,website/docs/guides/xai-grok-oauth.md)cli-config.yaml.exampleif I added/changed config keys — N/A (no new config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Aurlparse+input), no platform-specific codeScreenshots / Logs
Before the fix (issue #26923 reproduction on GitHub Codespaces —
CODESPACES=1, noSSH_CLIENT):After the fix:
The hint when running without
--manual-pastein a detected remote now ends with: