santifer/career-ops · error · Error
gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET /…
Error message
gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL_REFRESH_TOKEN in .env
What it means
The gmail plugin's ingest() requires three OAuth credentials from the environment: GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, and GMAIL_REFRESH_TOKEN. If any is missing or empty, it throws immediately with a message naming all three.
Solutions
- Add all three: GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REFRESH_TOKEN to .env.
- If you lack a refresh token, complete the OAuth consent flow (e.g. OAuth 2.0 playground with your client id/secret, scope https://www.googleapis.com/auth/gmail.readonly) and store the refresh token.
- Ensure your runner actually loads .env and the variable names match exactly.
- Restart the process after editing .env.
Example fix
// before GMAIL_CLIENT_ID=xxx.apps.googleusercontent.com # missing secret and refresh token // after GMAIL_CLIENT_ID=xxx.apps.googleusercontent.com GMAIL_CLIENT_SECRET=xxxxxxxx GMAIL_REFRESH_TOKEN=1//xxxxxxxx
Defensive patterns
Strategy: validation
Validate before calling
const required = ['GMAIL_CLIENT_ID','GMAIL_CLIENT_SECRET','GMAIL_REFRESH_TOKEN'];
const missing = required.filter(k => !process.env[k]);
if (missing.length) throw new Error(`missing Gmail env vars: ${missing.join(', ')}`); Try / catch
try {
await gmailPlugin.ingest(ctx);
} catch (e) {
if (String(e.message).includes('missing GMAIL_CLIENT_ID')) {
console.error('Add GMAIL_CLIENT_ID/SECRET/REFRESH_TOKEN to .env');
return [];
}
throw e;
} Prevention
- Complete the OAuth consent flow once and store all three credentials together.
- Add a startup env preflight listing all plugin-required variables.
- Keep .env.example in sync with required Gmail keys.
- Verify env loading in each runtime (local, docker, CI) before enabling the plugin.
When it happens
Trigger: Calling ingest (gmail ingestion during scan/pipeline) when any of the three env vars is unset — typically a partially filled .env with only some credentials, or none at all.
Common situations: Setting up Gmail ingestion and copying only client id/secret but never completing the OAuth flow for a refresh token; .env not loaded in the runtime (docker/CI); variable name typos (GMAIL_REFRESH_TOKEN vs GMAIL_REFRESH_TOKEN_).
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- Gmail token refresh failed
- Gmail token refresh returned no access_token
- APIFY_TOKEN not set
- APIFY_TOKEN not set — enable apify in config/plugins.yml…
- gmail: could not persist processed-id state
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/5660e633ace70ba5.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/gmail/index.mjs:77
}
function saveProcessedIds(ids) {
try {
mkdirSync('data', { recursive: true });
writeFileSync(STATE_PATH, JSON.stringify({ processed_message_ids: [...ids] }, null, 2), 'utf-8');
} catch (err) {
console.warn(`gmail: could not persist processed-id state — ${err.message}`);
}
}
/** @type {{ ingest: (ctx: any) => Promise<object[]> }} */
export default {
async ingest(ctx) {
const clientId = ctx?.env?.GMAIL_CLIENT_ID;
const clientSecret = ctx?.env?.GMAIL_CLIENT_SECRET;
const refreshToken = ctx?.env?.GMAIL_REFRESH_TOKEN;
if (!clientId || !clientSecret || !refreshToken) {
throw new Error('gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL_REFRESH_TOKEN in .env');
}
const label = ctx?.settings?.label || 'Job Leads';
const daysBack = Number(ctx?.settings?.days_back ?? 7);
if (!Number.isInteger(daysBack) || daysBack <= 0) {
throw new Error(`gmail: invalid days_back "${ctx?.settings?.days_back}" (must be a positive integer)`);
}
const token = await getAccessToken({ clientId, clientSecret, refreshToken }, ctx.fetch);
const auth = { Authorization: `Bearer ${token}` };
const query = `label:"${label}" newer_than:${daysBack}d`;
ctx.log(`gmail: querying ${query}`);
// List message ids (paginated). ctx.fetch throws on a non-2xx (with the body
// in the message), so a failed page surfaces a clear error.
const messages = [];
let pageToken = null;
do {View on GitHub (pinned to aac998c7ed)