{"record":{"id":"5660e633ace70ba5","repo":"santifer/career-ops","slug":"gmail-missing-gmail-client-id-gmail-client-secr","errorCode":null,"errorMessage":"gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL_REFRESH_TOKEN in .env","messagePattern":"gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL_REFRESH_TOKEN in \\.env","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/gmail/index.mjs","lineNumber":77,"sourceCode":"}\n\nfunction saveProcessedIds(ids) {\n  try {\n    mkdirSync('data', { recursive: true });\n    writeFileSync(STATE_PATH, JSON.stringify({ processed_message_ids: [...ids] }, null, 2), 'utf-8');\n  } catch (err) {\n    console.warn(`gmail: could not persist processed-id state — ${err.message}`);\n  }\n}\n\n/** @type {{ ingest: (ctx: any) => Promise<object[]> }} */\nexport default {\n  async ingest(ctx) {\n    const clientId = ctx?.env?.GMAIL_CLIENT_ID;\n    const clientSecret = ctx?.env?.GMAIL_CLIENT_SECRET;\n    const refreshToken = ctx?.env?.GMAIL_REFRESH_TOKEN;\n    if (!clientId || !clientSecret || !refreshToken) {\n      throw new Error('gmail: missing GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL_REFRESH_TOKEN in .env');\n    }\n\n    const label = ctx?.settings?.label || 'Job Leads';\n    const daysBack = Number(ctx?.settings?.days_back ?? 7);\n    if (!Number.isInteger(daysBack) || daysBack <= 0) {\n      throw new Error(`gmail: invalid days_back \"${ctx?.settings?.days_back}\" (must be a positive integer)`);\n    }\n\n    const token = await getAccessToken({ clientId, clientSecret, refreshToken }, ctx.fetch);\n    const auth = { Authorization: `Bearer ${token}` };\n    const query = `label:\"${label}\" newer_than:${daysBack}d`;\n    ctx.log(`gmail: querying ${query}`);\n\n    // List message ids (paginated). ctx.fetch throws on a non-2xx (with the body\n    // in the message), so a failed page surfaces a clear error.\n    const messages = [];\n    let pageToken = null;\n    do {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugins/gmail/index.mjs#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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_).","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."],"exampleFix":"// before\nGMAIL_CLIENT_ID=xxx.apps.googleusercontent.com\n# missing secret and refresh token\n// after\nGMAIL_CLIENT_ID=xxx.apps.googleusercontent.com\nGMAIL_CLIENT_SECRET=xxxxxxxx\nGMAIL_REFRESH_TOKEN=1//xxxxxxxx","handlingStrategy":"validation","validationCode":"const required = ['GMAIL_CLIENT_ID','GMAIL_CLIENT_SECRET','GMAIL_REFRESH_TOKEN'];\nconst missing = required.filter(k => !process.env[k]);\nif (missing.length) throw new Error(`missing Gmail env vars: ${missing.join(', ')}`);","typeGuard":null,"tryCatchPattern":"try {\n  await gmailPlugin.ingest(ctx);\n} catch (e) {\n  if (String(e.message).includes('missing GMAIL_CLIENT_ID')) {\n    console.error('Add GMAIL_CLIENT_ID/SECRET/REFRESH_TOKEN to .env');\n    return [];\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["gmail","env","oauth","credentials"],"backgroundTag":"missing-env-var","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}