{"record":{"id":"13ae01ad7c76c96c","repo":"garrytan/gstack","slug":"load-html-from-file-json-parse-failed-e-mess","errorCode":null,"errorMessage":"load-html: --from-file JSON parse failed: ${e.message}","messagePattern":"load-html: --from-file JSON parse failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/write-commands.ts","lineNumber":209,"sourceCode":"          const payloadPath = args[++i];\n          if (!payloadPath) throw new Error('load-html: --from-file requires a path');\n          // Parity with the sibling `load-html <file>` path below (line 249):\n          // that branch runs every `file://` target through validateReadPath\n          // so the safe-dirs policy can't be side-stepped. Same policy must\n          // apply here — otherwise --from-file becomes a read-anywhere escape\n          // hatch for any caller that can pick the payload path (e.g., an\n          // MCP caller issuing load-html with an attacker-influenced path).\n          try {\n            validateReadPath(path.resolve(payloadPath));\n          } catch {\n            throw new Error(\n              `load-html: --from-file ${payloadPath} must be under ${SAFE_DIRECTORIES.join(' or ')} (security policy). Copy the payload into the project tree or /tmp first.`\n            );\n          }\n          const raw = fs.readFileSync(payloadPath, 'utf8');\n          let json: any;\n          try { json = JSON.parse(raw); }\n          catch (e: any) { throw new Error(`load-html: --from-file JSON parse failed: ${e.message}`); }\n          if (typeof json.html !== 'string') {\n            throw new Error('load-html: --from-file JSON must have a \"html\" string field');\n          }\n          if (json.waitUntil && json.waitUntil !== 'load'\n              && json.waitUntil !== 'domcontentloaded' && json.waitUntil !== 'networkidle') {\n            throw new Error(`load-html: --from-file waitUntil '${json.waitUntil}' invalid`);\n          }\n          fromFilePayload = { html: json.html, waitUntil: json.waitUntil };\n        } else if (args[i] === '--wait-until') {\n          const val = args[++i];\n          if (val !== 'load' && val !== 'domcontentloaded' && val !== 'networkidle') {\n            throw new Error(`Invalid --wait-until '${val}'. Must be one of: load, domcontentloaded, networkidle.`);\n          }\n          waitUntil = val;\n        } else if (args[i].startsWith('--')) {\n          throw new Error(`Unknown flag: ${args[i]}`);\n        } else if (!filePath) {\n          filePath = args[i];","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/write-commands.ts#L191-L227","documentation":"Parse guard at write-commands.ts:209. The file at payloadPath passed validateReadPath and was read as UTF-8, but JSON.parse(raw) threw. The --from-file contract is a JSON wrapper {html, waitUntil?}, not raw HTML or any other format.","triggerScenarios":"Pointing --from-file at: a raw .html file; plaintext; a JSON file with trailing commas, single quotes, or comments; a file with a leading BOM; binary/corrupted content.","commonSituations":"Passing an .html file to --from-file instead of the JSON envelope; copy-paste introducing smart quotes or truncation; CRLF/BOM from a Windows editor.","solutions":["Wrap the HTML in a JSON object: {\"html\": \"<your html>\"}","Validate with a JSON linter: jq . payload.json","For a raw HTML file, use 'load-html <file>' (the file-path branch) instead of --from-file","Strip BOM and re-save as UTF-8 without BOM"],"exampleFix":"// before — raw HTML passed to --from-file (fails JSON.parse)\nawait handleWriteCommand('load-html', ['--from-file','page.html'], session, bm)\n// after — either wrap in JSON, or use the file path branch\nawait handleWriteCommand('load-html', ['page.html'], session, bm)","handlingStrategy":"try-catch","validationCode":"function isJsonFile(p: string): boolean {\n  try { JSON.parse(fs.readFileSync(p, 'utf8')); return true } catch { return false }\n}","typeGuard":null,"tryCatchPattern":"let json: any\ntry { json = JSON.parse(raw) }\ncatch (e) {\n  // fall back to the raw-HTML file path branch instead of --from-file\n  await handleWriteCommand('load-html', [payloadPath], session, bm)\n  return\n}","preventionTips":["Use --from-file only for the {html, waitUntil?} JSON envelope","For raw .html files, use 'load-html <file>' instead","Validate payloads with a JSON linter before loading"],"tags":["json","load-html","parsing","write-commands"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}