santifer/career-ops · error · Error
Gmail token refresh returned no access_token
Error message
Gmail token refresh returned no access_token
What it means
Thrown by `getAccessToken` in the gmail plugin (plugins/gmail/index.mjs:47) when the token-refresh POST returns HTTP 200 OK but the JSON body has no `access_token` field. Google's token endpoint is expected to return `{ access_token, expires_in, ... }`; a 200 with no access_token is an unexpected/ malformed success response. This guard prevents returning `undefined` and passing it downstream as a bearer token.
Source
Thrown at plugins/gmail/index.mjs:47
const STATE_PATH = 'data/gmail-state.json'; // the plugin's own processed-id cursor
/** Exchange the long-lived refresh token for a short-lived access token. */
async function getAccessToken({ clientId, clientSecret, refreshToken }, fetchFn = globalThis.fetch) {
const res = await fetchFn(TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
refresh_token: refreshToken,
grant_type: 'refresh_token',
}),
});
if (!res.ok) {
throw new Error(`Gmail token refresh failed: ${res.status} ${(await res.text()).slice(0, 200)}`);
}
const data = await res.json();
if (!data.access_token) throw new Error('Gmail token refresh returned no access_token');
return data.access_token;
}
function loadProcessedIds() {
if (!existsSync(STATE_PATH)) return new Set();
try {
const state = JSON.parse(readFileSync(STATE_PATH, 'utf-8'));
return new Set(state.processed_message_ids || []);
} catch {
return new Set();
}
}
function saveProcessedIds(ids) {
try {
mkdirSync('data', { recursive: true });
writeFileSync(STATE_PATH, JSON.stringify({ processed_message_ids: [...ids] }, null, 2), 'utf-8');
} catch (err) {View on GitHub (pinned to 9b17a8ac97)
Solutions
- Log `data` (without secrets) to see the actual response shape returned.
- Confirm TOKEN_URL points at `https://oauth2.googleapis.com/token`.
- If a proxy is intercepting, bypass it for oauth2.googleapis.com.
- Retry once — if persistent and the body is clearly not a token response, investigate the network path.
Defensive patterns
Strategy: validation
Validate before calling
// Sanity-check the token endpoint response shape in a dry-run.
async function probeTokenEndpoint(url = TOKEN_URL) {
// A harmless OPTIONS probe to confirm the host is the real Google endpoint.
const res = await fetch(url, { method: 'OPTIONS' });
if (!res.ok && res.status !== 405) {
throw new Error(`Token endpoint ${url} looks intercepted (status ${res.status}).`);
}
}
await probeTokenEndpoint(); Type guard
/** @param {unknown} data */
function hasAccessToken(data) {
return data != null && typeof data.access_token === 'string' && data.access_token.length > 0;
} Try / catch
try {
const token = await getAccessToken(creds);
} catch (err) {
if (/returned no access_token/.test(err.message)) {
console.error(`Unexpected token response — possible proxy interception: ${err.message}`);
// investigate the network path; do not coerce undefined into a bearer
} else throw err;
} Prevention
- Bypass corporate/captive proxies for oauth2.googleapis.com.
- Confirm TOKEN_URL points at https://oauth2.googleapis.com/token.
- Log the response keys (never values) when the shape is unexpected.
When it happens
Trigger: Google returns 200 but the JSON lacks `access_token` — e.g. an unexpected envelope, a response from an intercepting proxy, or an API anomaly. `if (!data.access_token)` fires after `res.json()`.
Common situations: A captive portal / corporate proxy returning a 200 HTML-or-JSON page that is not the real token response; a Google API transient anomaly; a misconfigured TOKEN_URL pointing at the wrong endpoint; response parsing returning an unexpected shape.
Related errors
- Gmail token refresh failed: ${res.status} ${(await res.text(
- Apify did not return a run id: ${JSON.stringify(body).slice(
- gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL
- Notion ${method} ${path} -> ${j.code}: ${j.message}
- remoteok: unexpected API response — expected a JSON array, g
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/d819801dab7eb5a1.
Report an issue: GitHub.