garrytan/gstack · error · Error
load-html: --from-file html does not start with a valid mark
Error message
load-html: --from-file html does not start with a valid markup opener
What it means
Content-sniff guard at write-commands.ts:241-243. After trimming leading whitespace, the html string must match /^<[a-zA-Z!?]/ — i.e. it begins with a tag character, '!' (doctype/comment), or '?' (XML prolog). This rejects non-markup content so the --from-file path doesn't silently render JSON, plaintext, or data as a blank page.
Source
Thrown at browse/src/write-commands.ts:242
} 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);
if (Buffer.byteLength(fromFilePayload.html, 'utf8') > MAX_BYTES) {
throw new Error(
`load-html: --from-file html too large (> ${MAX_BYTES} bytes). ` +
'Raise with GSTACK_BROWSE_MAX_HTML_BYTES=<N>.'
);
}
const peek = fromFilePayload.html.trimStart();
if (!/^<[a-zA-Z!?]/.test(peek)) {
throw new Error('load-html: --from-file html does not start with a valid markup opener');
}
const finalWaitUntil = fromFilePayload.waitUntil ?? waitUntil;
await session.setTabContent(fromFilePayload.html, { waitUntil: finalWaitUntil });
return `Loaded HTML: (inline from --from-file, ${fromFilePayload.html.length} chars)`;
}
if (!filePath) throw new Error('Usage: browse load-html <file> [--wait-until load|domcontentloaded|networkidle] [--tab-id <N>] | load-html --from-file <payload.json> [--tab-id <N>]');
// Extension allowlist
const ALLOWED_EXT = ['.html', '.htm', '.xhtml', '.svg'];
const ext = path.extname(filePath).toLowerCase();
if (!ALLOWED_EXT.includes(ext)) {
throw new Error(
`load-html: file does not appear to be HTML. Expected .html/.htm/.xhtml/.svg, got ${ext || '(no extension)'}. Rename the file if it's really HTML.`
);
}
const absolutePath = path.resolve(filePath);View on GitHub (pinned to 94993f7401)
Solutions
- Ensure html begins with a tag, <!doctype html>, <!-- comment -->, or <?xml prolog
- Strip any leading BOM and non-markup preamble
- Verify the html field holds markup, not the JSON envelope or a path
Example fix
// before — {"html": "Hello world"} (plain text, no opener)
// after — {"html": "<!doctype html><html>...Hello world...</html>"} Defensive patterns
Strategy: validation
Validate before calling
function startsLikeMarkup(html: string): boolean {
return /^<[a-zA-Z!?]/.test(html.trimStart())
} Type guard
function isMarkupOpenable(html: string): boolean {
return /^<[a-zA-Z!?]/.test(html.trimStart())
} Prevention
- Ensure html begins with a tag, <!doctype, <!--, or <?xml
- Strip leading BOM and non-markup preamble
- Confirm the field holds markup, not the JSON envelope or a path
When it happens
Trigger: html starting with plain text, a JSON object, a markdown heading, whitespace-only content, a BOM that survives trim, or '<' followed by a digit/space (e.g. '<123').
Common situations: Accidentally placing the JSON wrapper as the html value; HTML prefixed with a comment then leading text; markdown or templating output passed as HTML; a BOM-only or empty payload.
Related errors
- load-html: --from-file waitUntil '${json.waitUntil}' invalid
- Invalid --wait-until '${val}'. Must be one of: load, domcont
- 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/cf48896b4cd712e4.
Report an issue: GitHub.