santifer/career-ops · error
⚠️ Could not save report
Error message
⚠️ Could not save report: ${err.message} What it means
gemini-eval.mjs writes the evaluation report to reports/ and a one-row TSV to batch/tracker-additions/. When writeFileSync (or any sibling write in the try block) fails — bad path, read-only filesystem, ENOSPC, ENOENT parent directory — the tool warns, sets process.exitCode = 1, and skips the tracker merge, so the evaluation result exists only on stdout. The report generation itself succeeded; only persistence failed.
Solutions
- Read the err.message (ENOENT/EACCES/ENOSPC) and fix the root cause: mkdir -p reports batch/tracker-additions before running.
- Ensure the process has write permission to the repo directory (check ownership, container mounts, read-only FS).
- Free disk space if ENOSPC, or point output at a writable directory.
- Re-run the evaluation — the report was not persisted, so it must be regenerated (or copy it from stdout if captured).
Example fix
// before (assumes dirs exist)
writeFileSync(join(PATHS.reports, filename), report);
// after
import { mkdirSync } from 'node:fs';
mkdirSync(PATHS.reports, { recursive: true });
mkdirSync(join(CODE_ROOT, 'batch/tracker-additions'), { recursive: true });
writeFileSync(join(PATHS.reports, filename), report); Defensive patterns
Strategy: validation
Validate before calling
import { mkdirSync, accessSync, constants } from 'node:fs';
for (const dir of ['reports', 'batch/tracker-additions', 'data']) {
mkdirSync(dir, { recursive: true });
accessSync(dir, constants.W_OK); // throws early if not writable
} Try / catch
try {
writeFileSync(target, contents);
} catch (err) {
if (err.code === 'ENOENT') mkdirSync(dirname(target), { recursive: true });
if (err.code === 'ENOSPC') console.error('Disk full — free space and retry');
if (['EACCES', 'EROFS'].includes(err.code)) console.error(`No write permission on ${target}`);
process.exitCode = 1;
} Prevention
- Pre-create reports/ and batch/tracker-additions/ (mkdir -p) in your run script or Makefile.
- Check available disk space before large batch runs.
- Run inside a writable workspace — avoid read-only container mounts for the repo directory.
- Capture stdout when running headless so a failed save doesn't lose the report text.
When it happens
Trigger: writeFileSync(trackerPath, ...) or the report write throwing because the reports/ or batch/tracker-additions/ directory doesn't exist (ENOENT), the disk is full (ENOSPC), permissions deny writing (EACCES/EROFS), or an invalid character/permission issue on the target path.
Common situations: Fresh checkout where reports/ or batch/tracker-additions/ was never created (and is gitignored so absent after clone); running in a read-only container or CI workspace; disk quota exceeded after large batch runs; path constructed from a company slug containing characters invalid on the filesystem.
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
- ⚠️ not found at
- apify: JD cache write failed for
- ⚠️ Could not release report reservation
- ⚠️ Could not release report reservation
- ⚠️ Could not save HTML
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/78e262ecf818177f.
Report an issue: GitHub.
Appendix: source
Thrown at gemini-eval.mjs:585
];
// Optional `url` column, appended only when there is a real URL to put in
// it. merge-tracker.mjs matches on the URL FIRST -- the one tier that can
// prove two same-title rows are different openings -- so writing it here
// puts the row on that tier at merge time instead of leaving it to a
// later `--backfill-urls`. Label and value are appended together: the
// headed path resolves cells by NAME, so a value without its label would
// be dropped, and a label without its value would leave the url cell
// absent (#3517).
const trackerHeader = postingUrl ? `${TSV_ADDITION_HEADER}\turl` : TSV_ADDITION_HEADER;
if (postingUrl) trackerFields.push(tsvSafe(postingUrl));
// Header row first: merge-tracker resolves the fields by name, so this
// row cannot be ingested into the wrong columns (#3517).
writeFileSync(trackerPath, `${trackerHeader}\n${trackerFields.join('\t')}\n`, 'utf-8');
console.log(`\n✅ Report saved: reports/${filename}`);
console.log(`📊 Tracker addition saved: batch/tracker-additions/${num}-${companySlug}.tsv`);
reportSaved = true;
} catch (err) {
console.warn(`⚠️ Could not save report: ${err.message}`);
process.exitCode = 1;
}
if (reportSaved) {
try {
const mergeOutput = execFileSync(process.execPath, [join(CODE_ROOT, 'merge-tracker.mjs')], {
cwd: CODE_ROOT,
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 30000,
});
if (mergeOutput.trim()) console.log(mergeOutput.trim());
console.log('📊 Tracker merged into data/applications.md.');
} catch (err) {
console.warn(`⚠️ Report saved, but could not merge tracker addition into data/applications.md: ${err.message}`);
process.exitCode = 1;
}
}View on GitHub (pinned to aac998c7ed)