pbakaus/impeccable · error

font-match: cannot read {comp_file}: {e}

Error message

font-match: cannot read {comp_file}: {e}

What it means

Once the region is found, font-match loads the comp raster image named in the spec's `comp` field and crops the region pixels for font analysis. If `png_io::load_raster` fails (file missing, unreadable, not a valid PNG), the tool reports the failing path and the underlying cause with this message and exits 1.

Source

Thrown at crates/comp-verbs/src/font_match.rs:455

    let region = spec_val
        .get("regions")
        .and_then(Value::as_array)
        .and_then(|a| a.iter().find(|r| r.get("id").and_then(Value::as_str) == Some(id)))
        .cloned();
    let Some(region) = region else {
        let ids = spec_val
            .get("regions")
            .and_then(Value::as_array)
            .map(|a| a.iter().filter_map(|r| r.get("id").and_then(Value::as_str)).collect::<Vec<_>>().join(", "))
            .unwrap_or_default();
        io.err(&format!("font-match: no region {id}; ids: {ids}\n"));
        return 1;
    };
    let comp_file = spec_val.get("comp").and_then(Value::as_str).unwrap_or("").to_string();
    let comp = match png_io::load_raster(&resolve(io, &comp_file)) {
        Ok((d, _)) => d.image,
        Err(e) => {
            io.err(&format!("font-match: cannot read {comp_file}: {e}\n"));
            return 1;
        }
    };
    let px = |k: &str| region.pointer(&format!("/px/{k}")).and_then(Value::as_f64).unwrap_or(0.0);
    let (rx, ry, rw, rh) = (px("x"), px("y"), px("w"), px("h"));
    let c = r::crop(&comp, rx, ry, rw, rh);
    let fp = fingerprint(&c, &FpOpts::default());
    let px_w = rw as i64;
    let px_h = rh as i64;
    let Some(fp) = fp else {
        // No lettering: record the attempt and size by the box.
        if let Some(reg) = region_mut(spec_val, id) {
            let ty = reg.as_object_mut().and_then(|_| None::<()>);
            let _ = ty;
            let mut tmap = reg.get("type").and_then(|t| t.as_object()).cloned().unwrap_or_default();
            tmap.insert("comp".into(), Value::Null);
            tmap.insert("measuredAt".into(), json!(util::iso_now()));
            tmap.insert("note".into(), json!("no separable lettering in the crop; size by the region box"));

View on GitHub (pinned to 2bc2879276)

Solutions

  1. Restore or regenerate the comp PNG at the path in the spec's `comp` field (re-run the screenshot/comp-spec step).
  2. Check the recorded path: `jq -r '.comp' <spec>` and verify the file exists relative to your current directory.
  3. Run font-match from the same working directory used when the spec was created, or fix the stored path to be correct.
  4. Confirm the file is a valid, readable PNG (`file <path>`); replace it if corrupt.

Example fix

// before (spec.comp = "shot.png", file deleted)
node font-match.mjs --measure hero-title
// font-match: cannot read shot.png: No such file or directory
// after
node comp-spec.mjs --comp shot.png --auto   # regenerates spec + ensures comp path
node font-match.mjs --measure hero-title
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, readFileSync } from 'node:fs';
const spec = JSON.parse(readFileSync(specPath, 'utf8'));
const compPath = spec.comp;
if (!compPath || !existsSync(compPath)) {
  throw new Error(`comp image "${compPath}" missing — regenerate it before font-match`);
}
const magic = readFileSync(compPath).subarray(0, 8);
if (!magic.equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
  throw new Error(`${compPath} is not a valid PNG`);
}

Type guard

function compIsReadable(spec) {
  try {
    const p = spec?.comp;
    return typeof p === 'string' && p.length > 0 && existsSync(p) && statSync(p).size > 0;
  } catch { return false; }
}

Try / catch

try {
  runFontMatch(args);
} catch (e) {
  if (String(e.stderr).includes('cannot read')) {
    const path = String(e.stderr).match(/cannot read ([^:]+):/)?.[1];
    console.error(`Regenerate the comp image at ${path} (rerun screenshot/comp-spec)`);
    process.exit(1);
  } else throw e;
}

Prevention

When it happens

Trigger: The PNG recorded in the spec's `comp` field was deleted, moved, or renamed after comp-spec ran; the path is relative and font-match is run from a different working directory; the file exists but is corrupt or not a PNG; permissions prevent reading it.

Common situations: Cleaning up screenshots while keeping the spec; moving the project folder so relative comp paths break; a partial/failed download left a truncated PNG; committing the spec to git but ignoring the screenshot asset.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of pbakaus/impeccable@2bc2879276 (2026-09-08). Data as JSON: /api/errors/99f2facb5867e47c. Report an issue: GitHub.