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

  1. Ensure html begins with a tag, <!doctype html>, <!-- comment -->, or <?xml prolog
  2. Strip any leading BOM and non-markup preamble
  3. 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

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


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