{"record":{"id":"c6b81c68c1e45f60","repo":"garrytan/gstack","slug":"cannot-resolve-real-path-heatmappath-err-co","errorCode":null,"errorMessage":"Cannot resolve real path: ${heatmapPath} (${err.code})","messagePattern":"Cannot resolve real path: (.+?) \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/snapshot.ts","lineNumber":468,"sourceCode":"      const nodeFs = require('fs') as typeof import('fs');\n      const absolute = nodePath.resolve(heatmapPath);\n      const safeDirs = [TEMP_DIR, process.cwd()].map((d: string) => {\n        try { return nodeFs.realpathSync(d); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; return d; }\n      });\n      let realPath: string;\n      try {\n        realPath = nodeFs.realpathSync(absolute);\n      } catch (err: any) {\n        if (err.code === 'ENOENT') {\n          try {\n            const dir = nodeFs.realpathSync(nodePath.dirname(absolute));\n            realPath = nodePath.join(dir, nodePath.basename(absolute));\n          } catch (err2: any) {\n            if (err2?.code !== 'ENOENT') throw err2;\n            realPath = absolute;\n          }\n        } else {\n          throw new Error(`Cannot resolve real path: ${heatmapPath} (${err.code})`);\n        }\n      }\n      if (!safeDirs.some((dir: string) => isPathWithin(realPath, dir))) {\n        throw new Error(`Path must be within: ${safeDirs.join(', ')}`);\n      }\n    }\n\n    // Parse and validate color map\n    const VALID_COLORS = new Set(['green', 'yellow', 'red', 'blue', 'orange', 'gray']);\n    const COLOR_MAP: Record<string, { border: string; bg: string }> = {\n      green:  { border: '#00b400', bg: 'rgba(0,180,0,0.15)' },\n      yellow: { border: '#ffb400', bg: 'rgba(255,180,0,0.15)' },\n      red:    { border: '#ff0000', bg: 'rgba(255,0,0,0.15)' },\n      blue:   { border: '#0066ff', bg: 'rgba(0,102,255,0.15)' },\n      orange: { border: '#ff6600', bg: 'rgba(255,102,0,0.15)' },\n      gray:   { border: '#888888', bg: 'rgba(136,136,136,0.15)' },\n    };\n","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/snapshot.ts#L450-L486","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nsnapshot(page, { heatmap: '{\"@e1\":\"red\"}', outputPath: '/srv/http/hm.png' });\n// after\nsnapshot(page, { heatmap: '{\"@e1\":\"red\"}', outputPath: './out/hm.png' });","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nconst path = require('path');\nfunction assertHeatmapPathWritable(p: string): void {\n  const dir = path.dirname(path.resolve(p));\n  try { fs.accessSync(dir, fs.constants.W_OK | fs.constants.X_OK); }\n  catch (e: any) { throw new Error(`Cannot resolve real path: ${p} (${e.code})`); }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await snapshot(session, { heatmap: json, outputPath });\n} catch (e: any) {\n  if (/^Cannot resolve real path:/.test(e.message)) {\n    outputPath = path.join(require('os').tmpdir(), 'browse-heatmap.png');\n    await snapshot(session, { heatmap: json, outputPath });\n  } else throw e;\n}","preventionTips":["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."],"tags":["filesystem","permissions","path-resolution","heatmap","realpath"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}