Gemini OAuth: resolve npm global shim install layouts#26839
Gemini OAuth: resolve npm global shim install layouts#26839Phineas1500 wants to merge 1 commit intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR makes Gemini CLI OAuth credential discovery more robust across npm global installation “shim” layouts, so OpenClaw can locate and extract the bundled OAuth client credentials even when gemini on PATH is a wrapper script.
Changes:
- Expand credential discovery to consider multiple candidate Gemini CLI install roots derived from both the resolved executable path and the PATH bin directory.
- Fall back to scanning for
oauth2.jsacross those candidate roots when the known paths don’t match. - Add a regression test covering an npm global shim layout.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| extensions/google-gemini-cli-auth/oauth.ts | Adds multi-root resolution logic for locating Gemini CLI’s bundled oauth2.js across shim layouts. |
| extensions/google-gemini-cli-auth/oauth.test.ts | Adds a regression test for npm global shim installs to ensure credentials can still be extracted. |
| const candidates = [ | ||
| dirname(dirname(resolvedPath)), | ||
| join(dirname(resolvedPath), "node_modules", "@google", "gemini-cli"), | ||
| join(binDir, "node_modules", "@google", "gemini-cli"), | ||
| join(dirname(binDir), "node_modules", "@google", "gemini-cli"), | ||
| join(dirname(binDir), "lib", "node_modules", "@google", "gemini-cli"), | ||
| ]; | ||
|
|
There was a problem hiding this comment.
resolveGeminiCliDirs tries dirname(dirname(resolvedPath)) first. When resolvedPath is an npm shim/wrapper (common for global installs, especially on Windows), this can be a very broad directory (e.g. %APPDATA%//usr/local). The code then falls back to findFile(candidate, "oauth2.js", 10) against that broad directory, which (a) can trigger a large synchronous filesystem traversal before the later correct candidates are tried, and (b) increases the chance of accidentally matching an unrelated oauth2.js elsewhere on disk. Reorder candidates to try the most specific/likely package roots first (e.g. binDir/.../@google/gemini-cli, .../lib/node_modules/...), and/or only include the dirname(dirname(resolvedPath)) candidate (and/or recursive search) when resolvedPath appears to be inside the actual @google/gemini-cli install (e.g. contains node_modules/@google/gemini-cli).
| const candidates = [ | |
| dirname(dirname(resolvedPath)), | |
| join(dirname(resolvedPath), "node_modules", "@google", "gemini-cli"), | |
| join(binDir, "node_modules", "@google", "gemini-cli"), | |
| join(dirname(binDir), "node_modules", "@google", "gemini-cli"), | |
| join(dirname(binDir), "lib", "node_modules", "@google", "gemini-cli"), | |
| ]; | |
| const candidates: string[] = [ | |
| // Prefer specific, likely package roots first. | |
| join(dirname(resolvedPath), "node_modules", "@google", "gemini-cli"), | |
| join(binDir, "node_modules", "@google", "gemini-cli"), | |
| join(dirname(binDir), "node_modules", "@google", "gemini-cli"), | |
| join(dirname(binDir), "lib", "node_modules", "@google", "gemini-cli"), | |
| ]; | |
| // Only consider the broader parent-parent directory when it appears to be | |
| // within an actual @google/gemini-cli installation, to avoid scanning very | |
| // large, unrelated directories (for example, global npm shim locations). | |
| const parentParent = dirname(dirname(resolvedPath)); | |
| const normalizedParentParent = | |
| process.platform === "win32" | |
| ? parentParent.replace(/\\/g, "/").toLowerCase() | |
| : parentParent; | |
| if (/node_modules\/@google\/gemini-cli/i.test(normalizedParentParent)) { | |
| candidates.push(parentParent); | |
| } |
| it("extracts credentials when PATH entry is an npm global shim", async () => { | ||
| installNpmShimLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT }); | ||
|
|
||
| const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js"); | ||
| clearCredentialsCache(); | ||
| const result = extractGeminiCliCredentials(); | ||
|
|
||
| expect(result).toEqual({ | ||
| clientId: FAKE_CLIENT_ID, | ||
| clientSecret: FAKE_CLIENT_SECRET, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The npm-shim regression test validates the happy path, but it won’t catch a performance regression where extractGeminiCliCredentials() still falls back to the recursive findFile() scan before hitting the shim candidate path. Consider asserting that readdirSync/mockReaddirSync is not called in this test (or making mockReaddirSync throw and asserting it wasn’t invoked), so the test enforces that known-path resolution succeeds without a deep directory walk.
Summary
geminiis a wrapper entryWhy
Fixes onboarding/auth failures where Gemini CLI is installed and on PATH, but OpenClaw reports it as missing.
Fixes #9831
Testing
Greptile Summary
Robustifies Gemini CLI OAuth credential discovery to work across various npm global installation layouts (shims, symlinks, standard installs). The implementation now resolves multiple candidate install roots from both the resolved executable path and the PATH bin directory, fixing onboarding/auth failures where Gemini CLI is correctly installed and on PATH but OpenClaw couldn't locate its OAuth credentials.
Key changes:
resolveGeminiCliDirsfunction that generates multiple candidate directories for credential discoverydirname(dirname(resolvedPath))) and npm shim layouts (binDir/node_modules/@google/gemini-cli)The fix maintains backward compatibility with existing install layouts while expanding support for edge cases.
Confidence Score: 5/5
Last reviewed commit: ce779ef