Handle fnm Gemini OAuth config discovery#723
Merged
steipete merged 2 commits intosteipete:mainfrom Apr 18, 2026
Merged
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09dd4a83c1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
71f0127 to
b3e362a
Compare
b3e362a to
ae99e29
Compare
Gemini token refresh failed when the CLI was installed via fnm because CodexBar only looked for OAuth client credentials in legacy oauth2.js paths under node_modules. fnm installs can expose Gemini through fnm_multishells symlinks and bundle the CLI into bundle/gemini.js plus hashed chunk files, so neither the Node version directory nor the chunk filename is stable enough to hardcode. Update Gemini OAuth credential discovery to: - keep the cheap legacy oauth2.js path reads as the primary lookup - detect fnm-managed paths and resolve the active Gemini package root via fnm - support bundled CLI layouts by following bundle imports instead of assuming fixed chunk names - bound the directory walk-up so an unrelated Gemini install elsewhere on the host cannot contaminate discovery started from the actual binary path Also add API tests covering the legacy layouts and the fnm bundle layout so expired-token refresh works across both installation styles.
ae99e29 to
5060afd
Compare
Owner
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.
Fixes Gemini OAuth token refresh when the CLI is installed via fnm. Also
handles the Homebrew multi-level symlink chain that #497 aims to fix.
Symptom
Refreshing an expired Gemini access token fails with:
Root cause
The old discovery reads
oauth2.jsfrom a hard-coded list ofnode_modulespaths relative to the
geminibinary's parent directory. Two things breakthat assumption on fnm:
fnm_multishells/<shell-id>/bin/gemini, a singlerelative symlink into the same multishell's
lib/node_modules/@google/gemini-cli. The parent-directory math in theold code lands outside the package root.
bundle/gemini.jsplus hashedchunk-*.jsfiles instead ofdist/src/code_assist/oauth2.js. Thechunk filename is not stable across releases, so any hard-coded path
misses the OAuth constants.
Separately, Homebrew exposes
geminithrough a 4-level symlink chain(
/usr/local/bin/gemini→/opt/homebrew/bin/gemini→../Cellar/gemini-cli/<ver>/bin/gemini→../libexec/bin/gemini). Theold
destinationOfSymbolicLinkcall only resolved one level, so discoverylanded in
/opt/homebrew/bin/and missed thelibexec/tree.URL.resolvingSymlinksInPath()resolves the full chain, so the manualwalk is no longer needed.
Changes
extractOAuthCredentials()tries three strategies in order:oauth2.jspaths — Homebrew libexec, npm nested, Nix share,bun/npm sibling. Plain file reads, no subprocesses. Matches the pre-PR
code path.
fnm_multishells/fnm/node-versions) — runsfnm current, thenfnm exec --using <ver> npm root -gto get theglobal
node_modulesdirectory. Falls back tonode -p require.resolve('@google/gemini-cli/package.json')whennpm root -gdoes not expose the package.a
@google/gemini-cli/package.json(andlib/node_modules/@google/gemini-cli/package.jsonat each level).Bounded to 8 ascents so an unrelated Gemini install elsewhere on the
host cannot contaminate discovery.
Inside the resolved package root, the probe tries
dist/src/code_assist/oauth2.jsfirst, then follows relativeimport/exportstatements frombundle/gemini.jsuntil it finds the chunkholding
OAUTH_CLIENT_IDandOAUTH_CLIENT_SECRET.Tests
GeminiTestEnvironment.GeminiCLILayout.fnmBundlemirrors the real fnmmultishell layout: one relative symlink
bin/gemini -> ../lib/node_modules/@google/gemini-cli/bundle/gemini.js,with
package.jsonandbundle/*.jsas real files in the samemultishell directory.
refreshes expired token with fnm bundle layoutruns the full refreshflow through the probe.
client_id=test-client-idin the refreshrequest body. A developer machine with a real
@google/gemini-cliinstall would otherwise silently extract its OAuth constants. Without
the assertion the test passes through the wrong code path.
npmNestedandnixSharelayout tests still pass.Verification
swift test --no-parallelpasses locally and on CI (macOS + Linux CLIbuild).
the dev machine.