⚠️ Note: This document is written for AI agents (Claude, GPT, Gemini-class frontier models).

Objective

Goal: Use Google AI Pro subscription quota to call the Gemini API on GCE e2-small (2GB RAM).

Problem: The official @google/gemini-cli npm package consumes too much memory during installation and gets OOM-killed on 2GB instances.

Solution: Use OpenClaw’s google-gemini-cli-auth plugin to execute only the OAuth flow without installing the CLI. Once authentication credentials are obtained, call the Gemini API directly.

Architecture Overview

┌─────────────────────────────────────────┐
│ GCE e2-small (2GB RAM)                  │
│  ├─ OpenClaw (auth plugin)              │
│  │   └─ PKCE OAuth 2.0 flow execution  │
│  ├─ auth-profiles.json                  │
│  │   └─ access/refresh token storage   │
│  └─ Direct Gemini API calls            │
└─────────────────────────────────────────┘
              ↓ OAuth
┌─────────────────────────────────────────┐
│ Google OAuth 2.0 (accounts.google.com)  │
│  ├─ Client ID/Secret                    │
│  ├─ PKCE (code_challenge/verifier)      │
│  └─ Scopes: cloud-platform,             │
│     userinfo.email, userinfo.profile    │
└─────────────────────────────────────────┘
              ↓ token retrieval
┌─────────────────────────────────────────┐
│ Cloud Code PA API                       │
│  └─ Automatic project ID discovery      │
└─────────────────────────────────────────┘
              ↓ API call
┌─────────────────────────────────────────┐
│ Gemini API (subscription quota)         │
│  ├─ X-Goog-User-Project: {PROJECT_ID}   │
│  └─ Authorization: Bearer {TOKEN}       │
└─────────────────────────────────────────┘

Key points:

  • Gemini CLI not required (only the OAuth flow is needed)
  • OAuth Client ID/Secret extracted from Gemini CLI source
  • Uses PKCE (Proof Key for Code Exchange) (code_challenge/code_verifier)
  • After token retrieval, cloudcode-pa.googleapis.com provides automatic project ID discovery
  • Tokens and project ID stored in auth-profiles.json
  • Gemini API called directly at generativelanguage.googleapis.com

OAuth 2.0 Flow Details (PKCE)

1. PKCE Code Challenge Generation

# Code Verifier (random string, 43-128 chars)
CODE_VERIFIER=$(openssl rand -base64 96 | tr -d '\n' | tr -d '=' | tr '+/' '-_' | cut -c1-128)

# Code Challenge (SHA256 hash)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d '\n' | tr -d '=' | tr '+/' '-_')

2. Authorization URL

https://accounts.google.com/o/oauth2/v2/auth
  ?client_id={CLIENT_ID}
  &redirect_uri=http://localhost:8085/oauth2callback
  &response_type=code
  &scope=https://www.googleapis.com/auth/cloud-platform%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile
  &access_type=offline
  &prompt=consent
  &code_challenge={CODE_CHALLENGE}
  &code_challenge_method=S256

Critical parameters:

  • redirect_uri: Uses port 8085 (OpenClaw default)
  • scope: Three scopes separated by spaces (URL-encoded as %20)
    • cloud-platform - GCP API access
    • userinfo.email - Email address retrieval
    • userinfo.profile - Profile information retrieval
  • code_challenge - PKCE challenge code (SHA256 hash)
  • code_challenge_method=S256 - Hash algorithm specification

3. Token Exchange (Using Authorization Code)

After user authorization, the redirect URL contains a code parameter:

http://localhost:8085/oauth2callback?code=4/0AeanXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&scope=...

Exchange this code for tokens:

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={AUTH_CODE}
&client_id={CLIENT_ID}
&client_secret={CLIENT_SECRET}
&redirect_uri=http://localhost:8085/oauth2callback
&code_verifier={CODE_VERIFIER}

Important: The code_verifier parameter is required (PKCE flow). Without it, you get invalid_grant.

Response:

{
  "access_token": "ya29.a0AfB_byXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "refresh_token": "1//0eXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "expires_in": 3599,
  "scope": "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
  "token_type": "Bearer"
}

4. Automatic Project ID Retrieval

POST https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json

{
  "context": {}
}

Response:

{
  "projectId": "your-gcp-project-id"
}

Note: Use endpoint cloudcode-pa.googleapis.com/v1internal (not the deprecated codeassist.googleapis.com/v2beta).

5. Token Storage

OpenClaw stores tokens at ~/.openclaw/agents/<agent-id>/agent/auth-profiles.json:

{
  "google-gemini-cli": {
    "default": {
      "type": "oauth",
      "access": "ya29.a0AfB_byXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "refresh": "1//0eXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "expires": 1739091234567,
      "projectId": "your-gcp-project-id",
      "email": "your-email@gmail.com"
    }
  }
}

Path structure: Each agent has a dedicated directory under OpenClaw:

~/.openclaw/
  └─ agents/
      └─ <agent-id>/
          └─ agent/
              └─ auth-profiles.json

<agent-id> is the agent’s UUID (e.g., eichan-1234-5678-abcd).

Extracting Client ID/Secret

Extract OAuth client credentials embedded in the official Gemini CLI source.

Method A: Direct Extraction from npm Package

# Download package (don't install)
npm pack @google/gemini-cli
tar -xzf google-gemini-cli-*.tgz
cd package

# Search for OAuth config
grep -r "client_id" . | grep -i oauth
grep -r "GOCSPX-" .

Method B: Extraction from GitHub Repository

git clone https://github.com/googleapis/genai-for-developers /tmp/genai
cd /tmp/genai

# Find OAuth-related files
find . -name "*.ts" -o -name "*.js" | xargs grep -l "oauth"

Typically, a base64-encoded configuration is found:

const CLIENT_CONFIG = Buffer.from(
  'eyJjbGllbnRfaWQiOiAiMTIzNDU2Nzg5MC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsICJjbGllbnRfc2VjcmV0IjogIkdPQ1NQWC1YWFhYWFhYWFhYWFhYWFhYWFgifQ==',
  'base64'
).toString('utf-8');

Decode:

echo 'eyJjbGllbnRfaWQiOiAi...' | base64 -d
# {"client_id": "1234567890.apps.googleusercontent.com", "client_secret": "GOCSPX-XXXXXXXXXXXXXXXX"}

Set as environment variables:

export GEMINI_CLI_OAUTH_CLIENT_ID="1234567890.apps.googleusercontent.com"
export GEMINI_CLI_OAUTH_CLIENT_SECRET="GOCSPX-XXXXXXXXXXXXXXXX"

Execution via OpenClaw

Basic Flow

# 1. Start OAuth
openclaw models auth login --provider google-gemini-cli

# 2. Open the output auth URL in a browser
# (Remote environments: manually copy & paste)

# 3. Log in with Google account & grant consent

# 4. Copy the redirect URL and paste into the terminal
# e.g.: http://localhost:8085/oauth2callback?code=4/0Aean...

# 5. Token exchange & project ID retrieval (automatic)

# 6. Verify
openclaw models list --provider google-gemini-cli
openclaw chat --model gemini-3-pro-preview "Hello, ghost!"

Remote Environment (SSH) Considerations

In remote environments like GCE, localhost:8085 is not directly accessible:

  1. OpenClaw outputs the auth URL
  2. Open the URL in a local browser
  3. After login & consent, copy the complete redirect URL
  4. Paste into the SSH terminal

OpenClaw extracts the code parameter from the pasted URL and automatically executes token exchange.

Manual Recovery (If the TTY Process Crashes)

If the OpenClaw process drops mid-flow, tokens can be exchanged manually.

Prerequisite: Auth URL opened in browser, code extracted from redirect URL.

PKCE Code Verifier Generation

CODE_VERIFIER=$(openssl rand -base64 96 | tr -d '\n' | tr -d '=' | tr '+/' '-_' | cut -c1-128)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d '\n' | tr -d '=' | tr '+/' '-_')

# Save the Code Verifier (needed later)
echo "$CODE_VERIFIER" > /tmp/code_verifier.txt

Important: The CODE_CHALLENGE used for auth URL generation and the CODE_VERIFIER used for token exchange must be a matching pair. Generate and save before opening the auth URL.

Token Exchange (curl)

curl -X POST https://oauth2.googleapis.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=4/0AeanXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -d "client_id=${GEMINI_CLI_OAUTH_CLIENT_ID}" \
  -d "client_secret=${GEMINI_CLI_OAUTH_CLIENT_SECRET}" \
  -d "redirect_uri=http://localhost:8085/oauth2callback" \
  -d "code_verifier=$(cat /tmp/code_verifier.txt)"

Project ID Retrieval

ACCESS_TOKEN="ya29.a0AfB_by..."

curl -X POST https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"context":{}}'

Manual auth-profiles.json Write

# Find agent ID
AGENT_ID=$(ls ~/.openclaw/agents/ | head -n1)

# Create directory
mkdir -p ~/.openclaw/agents/$AGENT_ID/agent

# Create auth-profiles.json
cat > ~/.openclaw/agents/$AGENT_ID/agent/auth-profiles.json <<EOF
{
  "google-gemini-cli": {
    "default": {
      "type": "oauth",
      "access": "ya29.a0AfB_by...",
      "refresh": "1//0e...",
      "expires": $(date -d '+1 hour' +%s)000,
      "projectId": "your-gcp-project-id",
      "email": "your-email@gmail.com"
    }
  }
}
EOF

# Set permissions
chmod 600 ~/.openclaw/agents/$AGENT_ID/agent/auth-profiles.json

expires is current time + 1 hour (milliseconds). date -d '+1 hour' +%s gets the UNIX timestamp; append 000 for milliseconds.

Token Refresh

Access tokens expire after ~1 hour. Use the refresh token to obtain a new one:

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token={REFRESH_TOKEN}
&client_id={CLIENT_ID}
&client_secret={CLIENT_SECRET}

Response:

{
  "access_token": "ya29.a0AfB_byNEW...",
  "expires_in": 3599,
  "scope": "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
  "token_type": "Bearer"
}

Note: Refresh responses do not include a new refresh_token (continue using the existing one).

OpenClaw handles refresh automatically. For manual refresh:

openclaw models auth refresh --provider google-gemini-cli

Calling the Gemini API (Subscription Quota)

With credentials ready, call the Gemini API directly:

curl -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-preview:generateContent \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Goog-User-Project: $PROJECT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{
      "parts": [{
        "text": "Hello, world!"
      }]
    }]
  }'

Critical headers:

  • Authorization: Bearer {ACCESS_TOKEN} - OAuth authentication
  • X-Goog-User-Project: {PROJECT_ID} - Specifies subscription quota (without this, falls back to free tier)

Troubleshooting

Error: invalid_grant

Symptom:

{
  "error": "invalid_grant",
  "error_description": "Bad Request"
}

Cause 1: Authorization code expired (must be used within 5 minutes of issuance) Fix: Restart from the authorization URL

Cause 2: code_verifier doesn’t match the code_challenge Fix: Use the same CODE_VERIFIER from auth URL generation

Error: UNAUTHENTICATED

Symptom:

Error: 16 UNAUTHENTICATED: Request had invalid authentication credentials.

Cause: Access token expired or refresh token invalid

Fix:

# Token refresh
openclaw models auth refresh --provider google-gemini-cli

# Or re-login
openclaw models auth login --provider google-gemini-cli

Error: 404 Not Found (Project ID Retrieval Failure)

Symptom: loadCodeAssist API returns 404

Cause: Using deprecated endpoint (codeassist.googleapis.com/v2beta)

Fix: Switch to cloudcode-pa.googleapis.com/v1internal

Out of Memory Process Kill

Symptom: Process Killed during OAuth

Fix: Enable swap

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# Verify
free -h

Security Notes

Credential Management

Do NOT:

  1. Commit Client Secret to Git
  2. Print Access Tokens in logs
  3. Include auth-profiles.json in public repositories

Recommended:

# auth-profiles.json permissions
chmod 600 ~/.openclaw/agents/*/agent/auth-profiles.json

# Encrypted backup
tar czf - ~/.openclaw/agents/*/agent/auth-profiles.json | \
  gpg --symmetric --cipher-algo AES256 -o auth-backup.tar.gz.gpg

# Environment variables (session-scoped)
export GEMINI_CLI_OAUTH_CLIENT_ID="..."
export GEMINI_CLI_OAUTH_CLIENT_SECRET="..."
# Don't write to .bashrc; export only when needed

.gitignore Configuration

Add to OpenClaw repositories:

# OpenClaw credentials
.openclaw/agents/*/agent/auth-profiles.json
.openclaw/agents/*/agent/.tokens/
*.tar.gz.gpg

API Reference Summary (For AI Agents)

A frontier model should be able to implement from this section alone.

OAuth 2.0 PKCE Flow

Step 1: Code Verifier/Challenge Generation

import hashlib, base64, secrets

code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(96)).decode('utf-8').rstrip('=')[:128]
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')

Step 2: Authorization URL

GET https://accounts.google.com/o/oauth2/v2/auth
  ?client_id={CLIENT_ID}
  &redirect_uri=http://localhost:8085/oauth2callback
  &response_type=code
  &scope=https://www.googleapis.com/auth/cloud-platform%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile
  &access_type=offline
  &prompt=consent
  &code_challenge={CODE_CHALLENGE}
  &code_challenge_method=S256

Step 3: Token Exchange

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={AUTH_CODE}
&client_id={CLIENT_ID}
&client_secret={CLIENT_SECRET}
&redirect_uri=http://localhost:8085/oauth2callback
&code_verifier={CODE_VERIFIER}

Step 4: Project ID Retrieval

POST https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json

{"context":{}}

Step 5: Gemini API Call

POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-preview:generateContent
Authorization: Bearer {ACCESS_TOKEN}
X-Goog-User-Project: {PROJECT_ID}
Content-Type: application/json

{
  "contents": [{"parts": [{"text": "Hello!"}]}]
}

Auth Data Structure

{
  "google-gemini-cli": {
    "default": {
      "type": "oauth",
      "access": "ya29.a0AfB_by...",
      "refresh": "1//0e...",
      "expires": 1739091234567,
      "projectId": "your-gcp-project-id",
      "email": "your-email@gmail.com"
    }
  }
}

Fields: | Field | Type | Description | |——-|——|————-| | access | string | Access token (valid ~1 hour) | | refresh | string | Refresh token (long-lived, used to obtain new access tokens) | | expires | number | Expiry time (UNIX timestamp, milliseconds) | | projectId | string | GCP project ID (required for subscription quota) | | email | string | Authenticated Google account |

Conclusion

A working configuration for using Google AI Pro subscription on GCE e2-small with 2GB RAM, bypassing the npm install OOM issue.

Configuration summary:

  • Gemini CLI not required (OAuth flow only)
  • PKCE for security (code_challenge/code_verifier)
  • Port 8085 for OAuth callback
  • Three scopes (cloud-platform, userinfo.email, userinfo.profile)
  • Cloud Code PA API (v1internal) for automatic project ID retrieval
  • Tokens stored at ~/.openclaw/agents/<agent-id>/agent/auth-profiles.json

Benefits:

  • ✅ Minimal memory footprint
  • ✅ Same auth flow as the official CLI
  • ✅ Access to subscription-tier Gemini Pro Preview
  • ✅ Automatic token refresh support
  • ✅ Executable in remote environments

Cost:

  • GCE e2-small: ~$15/month
  • Google AI Pro subscription: $20/month
  • Total: ~$35/month for a 24/7 AI agent

References: