garrytan/gstack · warning · StrictModeError
image not found: ${src} (resolved to ${filePath})
Error message
image not found: ${src} (resolved to ${filePath}) What it means
StrictModeError thrown by inlineLocalImages() when a local image src resolves to a filePath that does not exist on disk (fs.existsSync is false), under --strict. In non-strict mode the same condition produces a warn and a missing-image placeholder; strict makes it fatal so broken image links fail the build rather than shipping a degraded PDF.
Source
Thrown at make-pdf/src/diagram-prepass.ts:635
// decodeURIComponent throws on malformed escapes (foo%zz.png) — a broken
// URL must degrade to the missing-image path, not crash the run.
let decodedSrc = src;
try {
decodedSrc = decodeURIComponent(src);
} catch { /* keep raw src */ }
const filePath = src.startsWith("file:")
? fileURLToPath(src)
: isDrivePath
? path.resolve(src)
: path.resolve(opts.inputDir, decodedSrc);
const cached = memo.get(filePath);
if (cached !== undefined) return rewriteImgTag(tag, cached);
if (!fs.existsSync(filePath)) {
const msg = `image not found: ${src} (resolved to ${filePath})`;
if (opts.strict) throw new StrictModeError(msg);
opts.warn(msg);
return buildMissingImagePlaceholder(src);
}
// Out-of-tree reads are legal (local CLI semantics — like pandoc) but
// never silent: an agent PDF-ing untrusted markdown should not quietly
// embed ~/.ssh/config into a shareable document. --strict makes it fatal.
// Compare REAL paths — a symlink inside the input dir pointing outside
// would otherwise pass a string-prefix check (Codex adversarial finding).
// Runs after the existence check: realpath of a missing file can't
// resolve, and on macOS /var vs /private/var would false-positive.
const inputRoot = safeRealpath(path.resolve(opts.inputDir)) + path.sep;
const realFilePath = safeRealpath(filePath);
if (!realFilePath.startsWith(inputRoot)) {
const msg = `image resolves OUTSIDE the input directory: ${src} → ${realFilePath}`;
if (opts.strict) throw new StrictModeError(msg + " — move it under the markdown's directory or drop --strict");
opts.warn(msg);
}View on GitHub (pinned to 94993f7401)
Solutions
- Verify the image exists at the resolved path printed in the message.
- Fix the markdown src to match the actual relative path from the markdown's directory.
- Ensure the assets directory is copied alongside the markdown in CI.
- Drop --strict to degrade to a warn + placeholder if a missing image is acceptable.
Example fix
<!-- before: file does not exist -->  <!-- actually committed as architecture.png --> <!-- after --> 
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
import path from 'node:path';
function findMissingImages(markdown: string, inputDir: string): string[] {
const re = /!\[[^\]]*\]\((?!https?:|data:)([^)]+)\)/g;
const missing: string[] = [];
let m: RegExpExecArray | null;
while ((m = re.exec(markdown))) {
const p = path.resolve(inputDir, decodeURIComponent(m[1]));
if (!fs.existsSync(p)) missing.push(m[1]);
}
return missing;
}
const missing = findMissingImages(markdown, inputDir);
if (missing.length) throw new Error(`missing images: ${missing.join(', ')}`); Type guard
import { StrictModeError } from './diagram-prepass';
function isStrictModeError(e: unknown): e is StrictModeError {
return e instanceof StrictModeError;
} Prevention
- Commit image assets alongside markdown in the same PR.
- Run a missing-image check in CI before the strict render.
- Use relative paths from the markdown's own directory consistently.
- Avoid case-mismatched filenames on case-sensitive filesystems.
When it happens
Trigger: inlineLocalImages() with opts.strict=true, an <img> whose decoded src path does not exist relative to opts.inputDir (or as a file:// URL / drive path). The existence check fails and the strict branch throws before any realpath or stat call.
Common situations: Markdown references an image that was never committed; a renamed asset whose markdown was not updated; wrong inputDir (CWD-relative resolution); a typo in the filename; a build that copies markdown but not its assets folder.
Related errors
- image is not a regular file: ${src}
- remote image blocked (offline posture): ${src} — re-run with
- image resolves OUTSIDE the input directory: ${src} → ${realF
- image exceeds ${Math.round(MAX_IMAGE_BYTES / 1024 / 1024)}MB
- input file not found: ${input}
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/d823cd0d7e5439ce.
Report an issue: GitHub.