Skip to content

Handle fnm Gemini OAuth config discovery#723

Merged
steipete merged 2 commits intosteipete:mainfrom
Leechael:fix/gemini-oauth-config-discovery
Apr 18, 2026
Merged

Handle fnm Gemini OAuth config discovery#723
steipete merged 2 commits intosteipete:mainfrom
Leechael:fix/gemini-oauth-config-discovery

Conversation

@Leechael
Copy link
Copy Markdown
Contributor

@Leechael Leechael commented Apr 15, 2026

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:

Gemini API error: Could not find Gemini CLI OAuth configuration

Root cause

The old discovery reads oauth2.js from a hard-coded list of node_modules
paths relative to the gemini binary's parent directory. Two things break
that assumption on fnm:

  1. The binary lives under fnm_multishells/<shell-id>/bin/gemini, a single
    relative symlink into the same multishell's
    lib/node_modules/@google/gemini-cli. The parent-directory math in the
    old code lands outside the package root.
  2. Recent Gemini CLI releases ship bundle/gemini.js plus hashed
    chunk-*.js files instead of dist/src/code_assist/oauth2.js. The
    chunk filename is not stable across releases, so any hard-coded path
    misses the OAuth constants.

Separately, Homebrew exposes gemini through a 4-level symlink chain
(/usr/local/bin/gemini/opt/homebrew/bin/gemini
../Cellar/gemini-cli/<ver>/bin/gemini../libexec/bin/gemini). The
old destinationOfSymbolicLink call only resolved one level, so discovery
landed in /opt/homebrew/bin/ and missed the libexec/ tree.
URL.resolvingSymlinksInPath() resolves the full chain, so the manual
walk is no longer needed.

Changes

extractOAuthCredentials() tries three strategies in order:

  1. Legacy oauth2.js paths — Homebrew libexec, npm nested, Nix share,
    bun/npm sibling. Plain file reads, no subprocesses. Matches the pre-PR
    code path.
  2. fnm-managed paths (fnm_multishells / fnm/node-versions) — runs
    fnm current, then fnm exec --using <ver> npm root -g to get the
    global node_modules directory. Falls back to
    node -p require.resolve('@google/gemini-cli/package.json') when
    npm root -g does not expose the package.
  3. Directory walk-up — from the resolved binary, walks up looking for
    a @google/gemini-cli/package.json (and
    lib/node_modules/@google/gemini-cli/package.json at 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.js first, then follows relative import /
export statements from bundle/gemini.js until it finds the chunk
holding OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET.

Tests

  • GeminiTestEnvironment.GeminiCLILayout.fnmBundle mirrors the real fnm
    multishell layout: one relative symlink
    bin/gemini -> ../lib/node_modules/@google/gemini-cli/bundle/gemini.js,
    with package.json and bundle/*.js as real files in the same
    multishell directory.
  • refreshes expired token with fnm bundle layout runs the full refresh
    flow through the probe.
  • All three refresh tests assert client_id=test-client-id in the refresh
    request body. A developer machine with a real @google/gemini-cli
    install would otherwise silently extract its OAuth constants. Without
    the assertion the test passes through the wrong code path.
  • Existing npmNested and nixShare layout tests still pass.

Verification

  • swift test --no-parallel passes locally and on CI (macOS + Linux CLI
    build).
  • Expired-token refresh works against a real fnm-installed Gemini CLI on
    the dev machine.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Sources/CodexBarCore/Providers/Gemini/GeminiStatusProbe.swift Outdated
@Leechael Leechael force-pushed the fix/gemini-oauth-config-discovery branch 2 times, most recently from 71f0127 to b3e362a Compare April 15, 2026 13:18
@Leechael Leechael marked this pull request as draft April 15, 2026 13:37
@Leechael Leechael force-pushed the fix/gemini-oauth-config-discovery branch from b3e362a to ae99e29 Compare April 15, 2026 13:43
@Leechael Leechael marked this pull request as ready for review April 15, 2026 14:00
Leechael and others added 2 commits April 18, 2026 21:24
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.
@steipete steipete force-pushed the fix/gemini-oauth-config-discovery branch from ae99e29 to 5060afd Compare April 18, 2026 20:28
@steipete steipete merged commit ad0aada into steipete:main Apr 18, 2026
1 check passed
@steipete
Copy link
Copy Markdown
Owner

Landed via temp rebase onto main.

  • Gate: swiftformat Sources Tests; swiftlint --strict; pnpm check; swift build; swift test -q; ./Scripts/compile_and_run.sh
  • Land commit: 5060afd
  • Merge commit: ad0aada

Thanks @Leechael!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants