santifer/career-ops · warning
apify: JD cache write failed for ${normalized.title} (${err.
Error message
apify: JD cache write failed for ${normalized.title} (${err.code || err.name}: ${err.message}); falling back to remote URL What it means
saveJd in plugins/apify/index.mjs caches scraped job descriptions to local files using writeFileSync with flag 'wx' (fail if the file already exists). On any write error other than the handled EEXIST case, it logs 'apify: JD cache write failed ... falling back to remote URL' and returns null so the pipeline can reference the remote posting URL instead of a local capture. This is a non-fatal degradation warning, not a crash.
Source
Thrown at plugins/apify/index.mjs:153
const today = new Date().toISOString().slice(0, 10);
const content = `---
title: ${yamlEscape(normalized.title)}
company: ${yamlEscape(normalized.company)}
url: ${yamlEscape(normalized.url)}
location: ${yamlEscape(normalized.location)}
scraped: "${today}"
source: ${sourceLabel}
---
# ${normalized.title} — ${normalized.company}
${descriptionBody}
`;
writeFileSync(filepath, content, { encoding: 'utf-8', flag: 'wx' });
return relPath;
} catch (err) {
if (err?.code === 'EEXIST' && relPath) return relPath;
console.warn(`apify: JD cache write failed for ${normalized.title} (${err.code || err.name}: ${err.message}); falling back to remote URL`);
return null;
}
}
export function normalizeItem(item, fieldMap, defaults) {
const out = {
title: String(pickField(item, fieldMap.title) || ''),
url: String(pickField(item, fieldMap.url) || ''),
company: fieldMap.company ? String(pickField(item, fieldMap.company) || '') : '',
location: fieldMap.location ? String(pickField(item, fieldMap.location) || '') : '',
};
for (const [k, v] of Object.entries(defaults || {})) {
if (!ALLOWED_DEFAULT_KEYS.has(k)) continue;
if (!out[k]) out[k] = String(v);
}
return out;
}
View on GitHub (pinned to 1696bec4d0)
Solutions
- Read the err.code in the warning (ENOENT → ensure the jds/ directory exists first, e.g. mkdirSync(dir, {recursive:true}); EACCES → fix permissions; ENOSPC → free disk space)
- Sanitize/shorten the job title used for the filename to avoid ENAMETOOLONG or invalid characters
- Ensure the script runs from the repository root (or make STATE/paths absolute) so the relative jds/ path lands in a writable location
- Treat the warning as informational: the pipeline continues using the remote URL; fix the environment and rerun to get a local capture
Example fix
// before
writeFileSync(filepath, content, { encoding: 'utf-8', flag: 'wx' });
// ENOENT because jds/ doesn't exist
// after: ensure the directory exists before writing
mkdirSync(path.dirname(filepath), { recursive: true });
writeFileSync(filepath, content, { encoding: 'utf-8', flag: 'wx' }); Defensive patterns
Strategy: fallback
Validate before calling
import { existsSync, accessSync, constants, statSync } from 'fs';
function cacheDirWritable(dir) {
if (!existsSync(dir)) return false; // caller must mkdir -p
try { accessSync(dir, constants.W_OK); return statSync(dir).isDirectory(); } catch { return false; }
}
// before saving: ensure dir exists and is writable, else expect the fallback warning Try / catch
const relPath = saveJd(item);
if (relPath === null) {
// cache write failed — proceed with the remote URL instead
reference = item.url;
} else {
reference = `local:${relPath}`;
} Prevention
- mkdirSync(dir, { recursive: true }) before writing JD captures
- Sanitize job titles for filename length and illegal characters
- Run the pipeline from the repo root (or use absolute paths) so jds/ is in a writable location
- Monitor disk space/quota in environments that scrape often
When it happens
Trigger: Calling saveJd when the target directory does not exist (ENOENT — the code creates the path from title/date but not parent dirs in all layouts), the process lacks write permission (EACCES/EPERM), the disk is full (ENOSPC), the path is invalid/too long (ENAMETOOLONG), or read-only filesystem (EROFS). EEXIST is explicitly tolerated and returns the existing relPath.
Common situations: Running the plugin in a read-only checkout or CI workspace where jds/ can't be created; disk quota exceeded after many scrapes; a job title with characters that produce an over-long or invalid filename; running from a different cwd so relative jds/ path resolves outside a writable area.
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
- ⚠️ Could not save report: ${err.message}
- cannot read ${rel || '.'}: ${err.message}
- refusing to hash symlink: ${childRel}
- refusing to hash non-regular file: ${childRel}
- Apify actor ${actorId} finished with status ${run.status}${r
AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01).
Data as JSON: /api/errors/a137adc9a5b2d4f7.
Report an issue: GitHub.