garrytan/gstack · error · Error

load-html: --from-file JSON parse failed: ${e.message}

Error message

load-html: --from-file JSON parse failed: ${e.message}

What it means

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.

Source

Thrown at browse/src/write-commands.ts:209

          const payloadPath = args[++i];
          if (!payloadPath) throw new Error('load-html: --from-file requires a path');
          // Parity with the sibling `load-html <file>` path below (line 249):
          // that branch runs every `file://` target through validateReadPath
          // so the safe-dirs policy can't be side-stepped. Same policy must
          // apply here — otherwise --from-file becomes a read-anywhere escape
          // hatch for any caller that can pick the payload path (e.g., an
          // MCP caller issuing load-html with an attacker-influenced path).
          try {
            validateReadPath(path.resolve(payloadPath));
          } catch {
            throw new Error(
              `load-html: --from-file ${payloadPath} must be under ${SAFE_DIRECTORIES.join(' or ')} (security policy). Copy the payload into the project tree or /tmp first.`
            );
          }
          const raw = fs.readFileSync(payloadPath, 'utf8');
          let json: any;
          try { json = JSON.parse(raw); }
          catch (e: any) { throw new Error(`load-html: --from-file JSON parse failed: ${e.message}`); }
          if (typeof json.html !== 'string') {
            throw new Error('load-html: --from-file JSON must have a "html" string field');
          }
          if (json.waitUntil && json.waitUntil !== 'load'
              && json.waitUntil !== 'domcontentloaded' && json.waitUntil !== 'networkidle') {
            throw new Error(`load-html: --from-file waitUntil '${json.waitUntil}' invalid`);
          }
          fromFilePayload = { html: json.html, waitUntil: json.waitUntil };
        } else if (args[i] === '--wait-until') {
          const val = args[++i];
          if (val !== 'load' && val !== 'domcontentloaded' && val !== 'networkidle') {
            throw new Error(`Invalid --wait-until '${val}'. Must be one of: load, domcontentloaded, networkidle.`);
          }
          waitUntil = val;
        } else if (args[i].startsWith('--')) {
          throw new Error(`Unknown flag: ${args[i]}`);
        } else if (!filePath) {
          filePath = args[i];

View on GitHub (pinned to 94993f7401)

Solutions

  1. Wrap the HTML in a JSON object: {"html": "<your html>"}
  2. Validate with a JSON linter: jq . payload.json
  3. For a raw HTML file, use 'load-html <file>' (the file-path branch) instead of --from-file
  4. Strip BOM and re-save as UTF-8 without BOM

Example fix

// before — raw HTML passed to --from-file (fails JSON.parse)
await handleWriteCommand('load-html', ['--from-file','page.html'], session, bm)
// after — either wrap in JSON, or use the file path branch
await handleWriteCommand('load-html', ['page.html'], session, bm)
Defensive patterns

Strategy: try-catch

Validate before calling

function isJsonFile(p: string): boolean {
  try { JSON.parse(fs.readFileSync(p, 'utf8')); return true } catch { return false }
}

Try / catch

let json: any
try { json = JSON.parse(raw) }
catch (e) {
  // fall back to the raw-HTML file path branch instead of --from-file
  await handleWriteCommand('load-html', [payloadPath], session, bm)
  return
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/13ae01ad7c76c96c. Report an issue: GitHub.