santifer/career-ops · error · Error

gmail: invalid days_back "${ctx?.settings?.days_back}" (must

Error message

gmail: invalid days_back "${ctx?.settings?.days_back}" (must be a positive integer)

What it means

The Gmail plugin reads settings.days_back from config/plugins.yml (ctx.settings) to scope the Gmail query to `newer_than:Nd`. It must coerce to a positive integer; the default is 7. This error fires when Number(settings.days_back) is non-integer or <= 0. Note the error string interpolates the RAW settings value (ctx?.settings?.days_back), not the coerced Number, so the message shows exactly what was configured.

Source

Thrown at plugins/gmail/index.mjs:83

  } 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 {
      let url = `${GMAIL_API}/messages?q=${encodeURIComponent(query)}`;
      if (pageToken) url += `&pageToken=${pageToken}`;
      const data = await (await ctx.fetch(url, { headers: auth })).json();
      if (data.messages) messages.push(...data.messages);
      pageToken = data.nextPageToken;
    } while (pageToken);

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Edit config/plugins.yml and set `days_back` to a positive integer (e.g. 7, 14, 30). The unit is days and is applied automatically — do NOT append 'd'.
  2. If you want the default, simply remove the days_back key (defaults to 7).
  3. Re-run the gmail plugin.

Example fix

# before (config/plugins.yml)
gmail:
  enabled: true
  label: "Job Leads"
  days_back: "7d"

# after
gmail:
  enabled: true
  label: "Job Leads"
  days_back: 7
Defensive patterns

Strategy: validation

Validate before calling

// Validate settings.days_back before letting the plugin read it.
function validDaysBack(settings) {
  const raw = settings?.days_back ?? 7;
  const n = Number(raw);
  return Number.isInteger(n) && n > 0 ? n : null;
}
const days = validDaysBack(ctx.settings);
if (days === null) throw new Error(`config error: gmail.days_back must be a positive integer, got ${JSON.stringify(ctx.settings?.days_back)}`);

Type guard

/** @param {unknown} v @returns {v is number} */
function isPositiveInt(v) {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}

Try / catch

try {
  await gmailPlugin.ingest(ctx);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('gmail: invalid days_back')) {
    ctx.log('Fix config/plugins.yml: gmail.days_back must be a positive integer (days), e.g. 7.');
  } else throw err;
}

Prevention

When it happens

Trigger: config/plugins.yml sets `gmail.days_back` to a non-numeric string (e.g. "one"), a float (e.g. 2.5), zero, a negative number, or null/non-string that Number() rejects (→ NaN, which fails Number.isInteger). Also when days_back is unset AND the `?? 7` default is somehow bypassed (it isn't, but a malformed override can shadow it).

Common situations: Typo in plugins.yml (days_back: "7d" — the unit is implicit, not appended); copy-pasting a duration string from another tool; setting days_back: 0 thinking it disables the filter.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/29821b03df0221ec. Report an issue: GitHub.