santifer/career-ops · warning
gmail: could not persist processed-id state
Error message
gmail: could not persist processed-id state — ${err.message} What it means
Warning in plugins/gmail/index.mjs's saveProcessedIds: persisting the processed Gmail message IDs to STATE_PATH (JSON in data/) failed. The IDs are then re-processed on the next ingest run, causing duplicate work; the error is logged and swallowed so the current ingest continues.
Solutions
- Check err.message for the fs error code (EACCES/ENOSPC/EPERM)
- Ensure the working directory is writable: mkdir -p data and verify permissions
- Avoid concurrent plugin runs sharing the same state file, or add file locking
- Re-run ingest; duplicates from the failed save are re-fetched and re-classified next time
Example fix
// before
mkdirSync('data', { recursive: true });
writeFileSync(STATE_PATH, JSON.stringify({ processed_message_ids: [...ids] }, null, 2), 'utf-8');
// after
mkdirSync(dirname(STATE_PATH), { recursive: true });
const tmp = STATE_PATH + '.tmp';
writeFileSync(tmp, JSON.stringify({ processed_message_ids: [...ids] }, null, 2), 'utf-8');
renameSync(tmp, STATE_PATH); // atomic, avoids partial state on crash Defensive patterns
Strategy: try-catch
Validate before calling
import { accessSync, constants } from 'fs';
try { accessSync('data', constants.W_OK); } catch { mkdirSync('data', { recursive: true }); } Type guard
function isFsError(e) { return e instanceof Error && typeof e.code === 'string'; } Try / catch
try { atomicWriteJson(STATE_PATH, { processed_message_ids: [...ids] }); } catch (err) { console.warn(`gmail: could not persist processed-id state — ${err.message}`); } Prevention
- Write state atomically (tmp file + rename) to survive crashes
- Never run two plugin instances against the same state file
- Confirm the repo/data dir is writable in CI sandboxes
- Re-run ingest after a failed save; duplicates are re-processed safely
When it happens
Trigger: writeFileSync to data/.gmail-state.json fails: data/ cannot be created (EACCES on mkdirSync despite recursive), disk full, read-only filesystem, or file locked by another process.
Common situations: Running the plugin in a sandbox/CI where the repo is read-only; concurrent plugin runs writing the state file simultaneously; disk quota exceeded.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- apify: JD cache write failed for
- ⚠️ Could not release report reservation
- ⚠️ Could not release report reservation
- ⚠️ Could not save HTML
- ⚠️ Could not save report
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/00d469ee16d91e89.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/gmail/index.mjs:66
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) {
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)`);
}View on GitHub (pinned to aac998c7ed)