I wanted Hank to read my calendar. Simple enough request — my OpenClaw AI assistant runs on a remote server, and I wanted it to pull today's events as part of the morning brief. I didn't realize that "read my Google calendar" would require creating a Google Cloud project, configuring an OAuth consent screen, navigating a multi-step remote auth flow, and then debugging a token expiry issue that took some digging to understand.
If you've been through this, you know. If you haven't, this is your preview.
Why All This Machinery Exists
Before walking through what happened, it helps to understand why Google's OAuth setup is the way it is.
When any application — including an AI agent — wants to access your Gmail, Google Drive, or Google Calendar, it needs to prove two things: who it is, and that you've consented to give it access. Google handles both through OAuth 2.0, and the infrastructure for that lives in Google Cloud Platform.
Here's how the pieces fit together:
GCP Project — The container for everything. Your OAuth app lives inside a project. If you don't have one, you need to create one before you can do anything else.
OAuth Consent Screen — The configuration that defines your 'app' — its name, the scopes it requests, and whether it's in Testing or Production mode. This is what you see when Google asks you to grant access.
OAuth Client — The actual credentials — a client ID and client secret — that your application uses to identify itself when requesting tokens. There can be multiple clients under one project.
None of this is Hank-specific or OpenClaw-specific. This is just how Google works for any third-party application that wants to access user data. The friction is real, and it's intentional — Google is protecting people from apps that request broad access without accountability.
For a personal AI assistant that only you use, it's a lot of ceremony for what feels like a simple thing. But it's a one-time setup, and once it's done it stays done — mostly.
The gog CLI and the Auth Flow
Hank uses a CLI tool called gog to interact with Google Workspace — Gmail, Calendar, Drive, Contacts, Sheets, and Docs. Once it's configured, it's straightforward: gog calendar events returns events, gog gmail send sends email, and so on.
The initial setup requires running gog auth add with your Google account email and the services you want to authorize. The experience here depends on where your agent is running.
If your agent runs locally on your machine, gog auth add opens a browser window automatically. The whole flow is seamless — sign in, approve, done.
If your agent runs on a remote server (which is my setup — Hank lives on a GCP VM), you need the --remote flag. This is where it gets interesting, and also where the agent can actually participate in the process.
When I ran into this, I didn't SSH into the server and run commands myself. I asked Hank to do it. The agent ran:
gog auth add agent-workspace@gmail.com --remote --step 1 \
--services gmail,calendar,drive,contacts,docs,sheets
Step 1 generates an authorization URL and prints it — Hank sent it to me via Telegram. I opened the URL in my local browser, signed in, approved the permissions, and Google redirected me to a callback URL like http://127.0.0.1:<port>/oauth2/callback?code=.... The page fails to load because nothing is listening on that port locally. That's expected. I copied the full URL from the address bar and sent it back to Hank, who then ran step 2:
gog auth add agent-workspace@gmail.com --remote --step 2 \
--auth-url 'http://127.0.0.1:<port>/oauth2/callback?code=...'
Hank extracted the authorization code from the URL, gog exchanged it with Google for an access token and a refresh token, and stored the refresh token on the server. The whole thing happened in the chat — no SSH session, no terminal on my end.
This is one of the better examples of what it actually feels like to work with an agent rather than just at it. The agent does the technical lifting; you handle the one thing only you can do (authenticate as yourself in a browser).
The 7-Day Expiry Nobody Tells You About
A few days after getting everything working, Hank's Google Workspace access stopped working entirely. The error was:
oauth2: "invalid_grant" "Token has been expired or revoked."
invalid_grant is vague. It could mean you revoked access, the token hasn't been used in six months, you changed your password, or a few other things. It doesn't tell you which one.
After some digging, the answer was in Google's OAuth documentation — buried in some common errors doc:
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of 'Testing' is issued a refresh token expiring in 7 days.
There it is. When your OAuth consent screen is in Testing mode, Google intentionally expires refresh tokens after 7 days. The intent is to prevent developers from leaving half-built apps with long-lived tokens floating around. It's a reasonable policy. It's also not well-advertised, and invalid_grant gives you no indication that this is what happened.
Here's the thing that trips people up: if you're a developer, you probably already know that access tokens are short-lived — they expire in about an hour and get refreshed automatically. Refresh tokens are supposed to be the long-lived part. That's true in Production. In Testing, the refresh token itself expires in a week whether you do anything or not.
If you're not a developer, here's the simpler version: there are two types of tokens involved. One is like a day pass — it expires quickly and gets renewed automatically. The other is like a key — it's supposed to last a long time. In Testing mode, Google makes that key expire after 7 days as a safety measure. Once it expires, you have to go through the whole authorization flow again.
The Fix: Publish to Production
The solution is to move the OAuth consent screen from Testing to Production. In Google Cloud Console, this is in the Audience section of the Google Auth Platform.
Publishing to Production removes the 7-day limit. The refresh token becomes long-lived. You re-authenticate once, and you don't have to do it again unless something changes.
The natural concern when you see "Publish app" is: does this make my app public? Does Google need to review it? Is there a cost?
Does it make the app public? — It makes the consent screen technically reachable by other Google accounts, but no one can access your data without you explicitly authorizing them. In practice, it's your private app that only you use.
Does Google need to review it? — Verification is required if you want to remove the 'unverified app' warning for other people who authorize your app. For a personal app that only you use, you can skip verification entirely — you'll see the warning when you re-auth and click through it once.
Is there a cost? — Publishing to Production is free. Formal verification itself is also free for most scope types — though apps requesting certain restricted scopes may require a third-party security assessment that has a cost. For a personal single-user app where you skip verification, none of this applies.
There's one additional thing worth knowing: Google can reclassify scope risk ratings over time. If a scope you're using gets upgraded to a more restricted tier, you'd receive email notifications and a grace period. Your app continues to work in the meantime. It's worth being aware of, not worth worrying about.
What the Unverified App Warning Actually Looks Like
When you re-authenticate after publishing to Production, you'll see a Google warning screen: "Google hasn't verified this app." It looks alarming if you're not expecting it.
This warning appears because the app requests sensitive OAuth scopes (Gmail, Calendar, Drive) and hasn't gone through Google's verification process. The warning exists to protect people from malicious third-party apps. For your own personal app, it's just noise.
To proceed: click Advanced, then "Go to [your app name] (unsafe)". Complete the permissions grant normally. You'll only see this when you re-authenticate, not on every API call.
The Full Fix
Publish the OAuth consent screen from Testing to Production in Google Cloud Console
Ask Hank to generate a fresh auth URL via
gog auth add --remoteOpen the URL in the browser, complete the flow, send the callback URL back
Verify with a test email
After that, calendar events worked again and the morning brief started including today's schedule. The root cause — Testing mode's 7-day token expiry — was something we hadn't encountered before. It's a subtle policy that produces a generic error message.
If you're setting up any application that uses Google OAuth and you expect it to run unattended for more than a week, publish to Production before you ship. You'll save yourself the debugging session.
Tools: gog CLI · Google Cloud Platform · OAuth 2.0
Originally published at https://www.paulbrennaman.me/lab/google-oauth-agent-access

