garrytan/gstack · error · Error
Cannot resolve real path: ${filePath} (${err.code})
Error message
Cannot resolve real path: ${filePath} (${err.code}) What it means
Thrown by validateReadPath when realpathSync fails with an errno other than ENOENT. ENOENT is handled separately (file does not exist yet, fall back to parent-dir check); any other failure — EACCES (permission denied), ELOOP (circular symlink), ENOTDIR (a path component is not a directory) — surfaces here with the raw errno code.
Source
Thrown at browse/src/path-security.ts:92
}
}
/** Validate a file path for reading (eval command). */
export function validateReadPath(filePath: string): void {
const resolved = path.resolve(filePath);
let realPath: string;
try {
realPath = fs.realpathSync(resolved);
} catch (err: any) {
if (err.code === 'ENOENT') {
try {
const dir = fs.realpathSync(path.dirname(resolved));
realPath = path.join(dir, path.basename(resolved));
} catch {
realPath = resolved;
}
} else {
throw new Error(`Cannot resolve real path: ${filePath} (${err.code})`);
}
}
const isSafe = SAFE_DIRECTORIES.some(dir => isPathWithin(realPath, dir));
if (!isSafe) {
throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
}
}
/** Validate a file path for remote serving (GET /file). TEMP_DIR only, not cwd. */
export function validateTempPath(filePath: string): void {
const resolved = path.resolve(filePath);
let realPath: string;
try {
realPath = fs.realpathSync(resolved);
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error('File not found');
}View on GitHub (pinned to 94993f7401)
Solutions
- Inspect permissions: `ls -la <path>` and `namei -l <path>` to see per-component perms
- chmod/chown the file or run the browse process as an authorized user
- Break symlink loops: `readlink -f <path>` to find the cycle, then remove the offending link
- Ensure every component except the last is a directory
Defensive patterns
Strategy: try-catch
Validate before calling
import * as fs from 'fs';
function canReadResolved(p: string): boolean {
try {
fs.realpathSync(p);
fs.accessSync(p, fs.constants.R_OK);
return true;
} catch (e: any) {
if (e.code === 'ENOENT') return true; // non-existent is fine for read validation
return false; // EACCES, ELOOP, ENOTDIR, etc.
}
}
if (!canReadResolved(filePath)) {
throw new Error(`Cannot read ${filePath}: fix permissions or symlink loops first`);
} Type guard
function isResolvableRealpath(p: string): boolean {
try { fs.realpathSync(p); return true; }
catch (e: any) { return e.code === 'ENOENT'; }
} Try / catch
try {
await runReadCommand(filePath);
} catch (e: any) {
if (/Cannot resolve real path/.test(e.message)) {
const m = e.message.match(/\((\w+)\)$/);
const code = m?.[1];
if (code === 'EACCES') console.error('Permission denied — chmod/chown the file or run as an authorized user.');
else if (code === 'ELOOP') console.error('Circular symlink — remove the loop with readlink -f.');
else if (code === 'ENOTDIR') console.error('A path component is not a directory.');
}
throw e;
} Prevention
- Check fs.accessSync(p, R_OK) before passing a path to eval
- Avoid symlink loops in dotfiles and test fixtures
- Run the browse process as a user with read access to the files it needs
- Use readlink -f to verify the real path is reachable before the command
When it happens
Trigger: Calling eval with a file path that hits a permission wall, a symlink loop, or a non-directory component. realpathSync throws and err.code is not 'ENOENT', so the original cause is re-exposed with its errno.
Common situations: File owned by another user with no read permission on the browse process; symlink loop created by misconfigured dotfiles; a regular file sitting where a directory was expected in the path; SELinux/AppArmor denial.
Related errors
- Cannot resolve path: ${filePath}
- Path must be within: ${SAFE_DIRECTORIES.join(', ')}
- Cannot resolve real path: ${screenshotPath} (${err.code})
- Cannot resolve real path: ${heatmapPath} (${err.code})
- Skill "${name}" not found in any tier.
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/5168a343e4576cb3.
Report an issue: GitHub.