{"record":{"id":"f9ff1ff1b29d0cbb","repo":"garrytan/gstack","slug":"file-not-found-fp","errorCode":null,"errorMessage":"File not found: ${fp}","messagePattern":"File not found: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/write-commands.ts","lineNumber":600,"sourceCode":"\n    case 'useragent': {\n      const ua = args.join(' ');\n      if (!ua) throw new Error('Usage: browse useragent <string>');\n      bm.setUserAgent(ua);\n      const error = await bm.recreateContext();\n      if (error) {\n        return `User agent set to \"${ua}\" but: ${error}`;\n      }\n      return `User agent set: ${ua}`;\n    }\n\n    case 'upload': {\n      const [selector, ...filePaths] = args;\n      if (!selector || filePaths.length === 0) throw new Error('Usage: browse upload <selector> <file1> [file2...]');\n\n      // Validate paths are within safe directories (same check as cookie-import)\n      for (const fp of filePaths) {\n        if (!fs.existsSync(fp)) throw new Error(`File not found: ${fp}`);\n        if (path.isAbsolute(fp)) {\n          let resolvedFp: string;\n          try { resolvedFp = fs.realpathSync(path.resolve(fp)); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; resolvedFp = path.resolve(fp); }\n          if (!SAFE_DIRECTORIES.some(dir => isPathWithin(resolvedFp, dir))) {\n            throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);\n          }\n        }\n        if (path.normalize(fp).includes('..')) {\n          throw new Error('Path traversal sequences (..) are not allowed');\n        }\n      }\n\n      const resolved = await session.resolveRef(selector);\n      if ('locator' in resolved) {\n        await resolved.locator.setInputFiles(filePaths);\n      } else {\n        await target.locator(resolved.selector).setInputFiles(filePaths);\n      }","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/write-commands.ts#L582-L618","documentation":"Thrown by `browse upload` inside the per-file validation loop when `fs.existsSync(fp)` returns false for one of the supplied file paths. The command refuses to forward a non-existent path to Playwright's `setInputFiles`, which would itself throw a less actionable error. Note the existence check runs BEFORE the safe-directory and traversal checks.","triggerScenarios":"Passing a relative path that does not resolve against the process cwd; a typo'd filename; a path on a different machine (remote agent context); a file that was deleted between snapshot time and upload time; a path with an unexpanded `~` (tilde is NOT expanded by Node's fs APIs).","commonSituations":"Agent captured an absolute path on the host but the browse server runs in a container with a different filesystem layout; user passes `~/Downloads/x.png` and Node does not expand `~`; file lives in a directory the user just `rm -rf`'d during cleanup; macOS case-insensitive FS masked a casing mismatch at authoring time but the browse server is on Linux (case-sensitive).","solutions":["Verify the path exists from the same process/cwd the browse server uses: `fs.existsSync(path.resolve(fp))`.","Expand `~` to `os.homedir()` before passing — Node does not do this for you.","Use an absolute path under TEMP_DIR or the project cwd (those are the safe directories).","If running in a container, ensure the file is volume-mounted into the container at the path you reference."],"exampleFix":"// before\nawait runBrowseCommand(['upload', 'input[type=file]', '~/Downloads/x.png']);\n\n// after\nimport os from 'os';\nimport path from 'path';\nconst fp = path.join(os.homedir(), 'Downloads', 'x.png');\nawait runBrowseCommand(['upload', 'input[type=file]', fp]);","handlingStrategy":"validation","validationCode":"import fs from 'fs';\nimport path from 'path';\nfunction ensureFilesExist(filePaths: string[], cwd = process.cwd()): void {\n  for (const fp of filePaths) {\n    const resolved = path.resolve(cwd, fp);\n    if (!fs.existsSync(resolved)) {\n      throw new Error(`File not found: ${fp} (resolved: ${resolved})`);\n    }\n  }\n}","typeGuard":"function fileExists(p: string): boolean {\n  try { return fs.statSync(p).isFile(); } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Expand '~' to os.homedir() before passing paths — Node does not expand it.","Use absolute paths resolved from the browse server's cwd, not the caller's shell cwd.","In containers, ensure the file is volume-mounted at the referenced path."],"tags":["filesystem","upload","file-not-found","browse-command"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}