garrytan/gstack · error · Error
load-html: --from-file waitUntil '${json.waitUntil}' invalid
Error message
load-html: --from-file waitUntil '${json.waitUntil}' invalid What it means
Validation at write-commands.ts:214-216. The payload's optional waitUntil is present but not one of load, domcontentloaded, networkidle. These are the only Playwright setContent wait strategies the command supports.
Source
Thrown at browse/src/write-commands.ts:215
// 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];
}
}
// Inline HTML path: validate size + magic byte, then setContent directly.
if (fromFilePayload) {
const MAX_BYTES = parseInt(process.env.GSTACK_BROWSE_MAX_HTML_BYTES || '', 10) || (50 * 1024 * 1024);View on GitHub (pinned to 94993f7401)
Solutions
- Set waitUntil to 'load', 'domcontentloaded', or 'networkidle'
- Omit waitUntil entirely to accept the default (domcontentloaded)
Example fix
// before — {"html":"...","waitUntil":"idle"}
// after — {"html":"...","waitUntil":"networkidle"} Defensive patterns
Strategy: validation
Validate before calling
const WAIT_UNTIL = new Set(['load','domcontentloaded','networkidle'])
function validPayloadWaitUntil(w: unknown): boolean {
return w == null || (typeof w === 'string' && WAIT_UNTIL.has(w))
} Type guard
function isWaitUntil(v: unknown): v is 'load'|'domcontentloaded'|'networkidle'|undefined {
return v == null || (typeof v === 'string' && ['load','domcontentloaded','networkidle'].includes(v))
} Prevention
- Omit waitUntil to accept the default
- Restrict to the three Playwright setContent strategies
- Validate the payload before loading
When it happens
Trigger: Payload JSON like {"html": "...", "waitUntil": "none"} or any value outside the allowed set (e.g. 'idle', 'ready', 'complete').
Common situations: Copying a waitUntil value from another tool's API; guessing a value; typo.
Related errors
- Invalid --wait-until '${val}'. Must be one of: load, domcont
- load-html: --from-file html does not start with a valid mark
- 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
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/02e58ca2d8b0901f.
Report an issue: GitHub.