garrytan/gstack · error · Error
load-html: --from-file JSON must have a "html" string field
Error message
load-html: --from-file JSON must have a "html" string field
What it means
Schema guard at write-commands.ts:211. The payload parsed as valid JSON, but json.html is missing or not a string (number/array/object/null/undefined). The contract requires a top-level string field literally named 'html'.
Source
Thrown at browse/src/write-commands.ts:211
// 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
- Ensure the top-level object has html as a string: {"html": "<html>...</html>"}
- Check the exact key spelling and casing
- Confirm the value is the markup string itself, not a path or structured object
Example fix
// before — {"content": "<html>..."} → no html field
// after
{"html": "<html>..."} Defensive patterns
Strategy: type-guard
Validate before calling
function hasHtmlString(json: unknown): json is { html: string } {
return typeof (json as any)?.html === 'string'
} Type guard
function isFromFilePayload(x: unknown): x is { html: string; waitUntil?: string } {
return typeof (x as any)?.html === 'string'
} Prevention
- Always include a top-level string field named exactly 'html'
- Validate the parsed shape before handing it to the command
- Confirm the value is markup, not a path or object
When it happens
Trigger: JSON without an html key; html set to a non-string; wrong key name (content, body, markup, source); html set to a file path string is actually fine type-wise but semantically wrong — but a missing/typed-wrong html triggers this.
Common situations: Using a different key name by convention; html set to null or an array of nodes; an empty {} object.
Related errors
- load-html: --from-file JSON parse failed: ${e.message}
- Cannot use load-html inside a frame. Run 'frame main' first.
- load-html: --from-file requires a path
- load-html: --from-file ${payloadPath} must be under ${SAFE_D
- load-html: --from-file waitUntil '${json.waitUntil}' invalid
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/e294f84af1a9d230.
Report an issue: GitHub.