garrytan/gstack · error · Error
Cannot resolve real path: ${heatmapPath} (${err.code})
Error message
Cannot resolve real path: ${heatmapPath} (${err.code}) What it means
Heatmap-mode twin of error 180. The path validator for opts.heatmap output calls realpathSync on heatmapPath and throws this when the syscall fails with any code other than ENOENT. Protects the heatmap writer from unreadable/looping/non-directory segments before page.screenshot() writes the file.
Source
Thrown at browse/src/snapshot.ts:468
const nodeFs = require('fs') as typeof import('fs');
const absolute = nodePath.resolve(heatmapPath);
const safeDirs = [TEMP_DIR, process.cwd()].map((d: string) => {
try { return nodeFs.realpathSync(d); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; return d; }
});
let realPath: string;
try {
realPath = nodeFs.realpathSync(absolute);
} catch (err: any) {
if (err.code === 'ENOENT') {
try {
const dir = nodeFs.realpathSync(nodePath.dirname(absolute));
realPath = nodePath.join(dir, nodePath.basename(absolute));
} catch (err2: any) {
if (err2?.code !== 'ENOENT') throw err2;
realPath = absolute;
}
} else {
throw new Error(`Cannot resolve real path: ${heatmapPath} (${err.code})`);
}
}
if (!safeDirs.some((dir: string) => isPathWithin(realPath, dir))) {
throw new Error(`Path must be within: ${safeDirs.join(', ')}`);
}
}
// Parse and validate color map
const VALID_COLORS = new Set(['green', 'yellow', 'red', 'blue', 'orange', 'gray']);
const COLOR_MAP: Record<string, { border: string; bg: string }> = {
green: { border: '#00b400', bg: 'rgba(0,180,0,0.15)' },
yellow: { border: '#ffb400', bg: 'rgba(255,180,0,0.15)' },
red: { border: '#ff0000', bg: 'rgba(255,0,0,0.15)' },
blue: { border: '#0066ff', bg: 'rgba(0,102,255,0.15)' },
orange: { border: '#ff6600', bg: 'rgba(255,102,0,0.15)' },
gray: { border: '#888888', bg: 'rgba(136,136,136,0.15)' },
};
View on GitHub (pinned to 94993f7401)
Solutions
- Pass --output-path inside cwd or TEMP_DIR with a writable parent chain.
- Decode the errno in parentheses: EACCES → chmod/chown the parent; ELOOP → break the symlink cycle; ENOTDIR → ensure all prefix segments are directories.
- Omit --output-path to use `${TEMP_DIR}/browse-heatmap.png`.
- Run `namei -l <path>` to find the first segment denying traversal.
Example fix
// before
snapshot(page, { heatmap: '{"@e1":"red"}', outputPath: '/srv/http/hm.png' });
// after
snapshot(page, { heatmap: '{"@e1":"red"}', outputPath: './out/hm.png' }); Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
const path = require('path');
function assertHeatmapPathWritable(p: string): void {
const dir = path.dirname(path.resolve(p));
try { fs.accessSync(dir, fs.constants.W_OK | fs.constants.X_OK); }
catch (e: any) { throw new Error(`Cannot resolve real path: ${p} (${e.code})`); }
} Try / catch
try {
await snapshot(session, { heatmap: json, outputPath });
} catch (e: any) {
if (/^Cannot resolve real path:/.test(e.message)) {
outputPath = path.join(require('os').tmpdir(), 'browse-heatmap.png');
await snapshot(session, { heatmap: json, outputPath });
} else throw e;
} Prevention
- Default heatmap outputPath to undefined so snapshot.ts uses TEMP_DIR.
- Verify the parent directory is writable+traversable before calling snapshot.
- Run `namei -l <parent>` to surface the first denying segment.
- In containers, mount a writable volume at the temp dir.
When it happens
Trigger: Calling snapshot with opts.heatmap set and an opts.outputPath (or its default) whose realpath cannot be resolved for a non-ENOENT reason: EACCES on a parent dir, ELOOP symlink cycle, ENOTDIR prefix.
Common situations: Same shape as 180 but on the heatmap branch: restrictive umask plus a /tmp mounted noexec; container where the temp dir is owned by root but the browser runs as nobody; a CI runner whose cwd is on a read-only mount.
Related errors
- Cannot resolve real path: ${screenshotPath} (${err.code})
- Cannot resolve real path: ${filePath} (${err.code})
- Cannot resolve path: ${filePath}
- Skill "${name}" not found in any tier.
- Skill "${name}" not found.
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/c6b81c68c1e45f60.
Report an issue: GitHub.