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

  1. Read the err.message (ENOENT/EACCES/ENOSPC) and fix the root cause: mkdir -p reports batch/tracker-additions before running.
  2. Ensure the process has write permission to the repo directory (check ownership, container mounts, read-only FS).
  3. Free disk space if ENOSPC, or point output at a writable directory.
  4. 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

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


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)