santifer/career-ops · error · Error

gmail: invalid days_back

Error message

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

What it means

The Gmail plugin validates the `days_back` setting before building its Gmail search query (`newer_than:{days}d`). It coerces the raw setting with Number() and throws if the result is not a positive integer. This catches non-numeric strings, zero, negatives, and undefined-but-present values so a malformed query is never sent to the Gmail API.

Solutions

  1. Set days_back to a plain positive integer in the plugin settings, e.g. days_back: 7 (no quotes, no units).
  2. Remove the days_back key entirely to get the default of 7 days.
  3. Inspect what the settings loader actually passes — log ctx.settings.days_back and its type; an empty string or 'null' string fails the check.
  4. If loading config from YAML/JSON, ensure the value is parsed as a number, not left as a string.

Example fix

// before
settings:
  label: Job Leads
  days_back: "7d"
// after
settings:
  label: Job Leads
  days_back: 7
Defensive patterns

Strategy: validation

Validate before calling

const raw = ctx?.settings?.days_back ?? 7;
const days = Number(raw);
if (!Number.isInteger(days) || days <= 0) {
  throw new TypeError(`days_back must be a positive integer, got: ${JSON.stringify(raw)}`);
}

Type guard

const isValidDaysBack = (v) => Number.isInteger(Number(v ?? 7)) && Number(v ?? 7) > 0;

Try / catch

try {
  await ingest(ctx);
} catch (e) {
  if (String(e.message).startsWith('gmail: invalid days_back')) {
    ctx.settings.days_back = 7; // fall back to default and retry
    await ingest(ctx);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the gmail ingest with ctx.settings.days_back set to something that is not a positive integer: a string like '7d' or 'week', 0, a negative number, a float like 1.5, or an empty string. Only an outright undefined/null value is defaulted to 7 — any other bad value throws.

Common situations: Users put `days_back: "last week"` or `days_back: "7d"` (with unit) in their plugin settings YAML; a settings loader passes an empty string; someone copies a config where days_back was left as a placeholder or set to 0 hoping for 'today'.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/29821b03df0221ec. Report an issue: GitHub.

Appendix: 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 aac998c7ed)