garrytan/gstack · warning · Error

load-html: --from-file requires a path

Error message

load-html: --from-file requires a path

What it means

Argument guard at write-commands.ts:192. The --from-file flag consumes the next token via args[++i]; if that token is undefined (--from-file is the last arg) or empty, payloadPath is falsy and the command refuses to proceed rather than reading an unintended path.

Solutions

  1. Supply the payload path immediately after the flag: load-html --from-file ./payload.json
  2. Validate the args array length in automation before invoking
  3. Ensure the path token is non-empty and present

Example fix

// before
await handleWriteCommand('load-html', ['--from-file'], session, bm)
// after
await handleWriteCommand('load-html', ['--from-file', './payload.json'], session, bm)
Defensive patterns

Strategy: validation

Validate before calling

function requireFromFilePath(args: string[], i: number): string {
  const p = args[i + 1]
  if (!p) throw new Error('--from-file requires a path')
  return p
}

Type guard

function hasFromFileTarget(args: string[]): boolean {
  const i = args.indexOf('--from-file')
  return i >= 0 && typeof args[i + 1] === 'string' && args[i + 1].length > 0
}

Prevention

When it happens

Trigger: load-html --from-file with no following token (last argument), or followed by an empty string; e.g. 'load-html --from-file' alone.

Common situations: Truncated command, a templating bug that drops the path, or quoting that yields an empty value.

Related errors


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

Appendix: source

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

      await page.reload({ waitUntil: 'domcontentloaded', timeout: 15000 });
      return `Reloaded ${page.url()}`;
    }

    case 'load-html': {
      if (inFrame) throw new Error('Cannot use load-html inside a frame. Run \'frame main\' first.');

      // --from-file <path.json>: read inline HTML from a JSON payload. Used by
      // make-pdf to dodge Windows argv size limits on large rendered HTML.
      // The JSON shape is { html: string, waitUntil?: "load"|"domcontentloaded"|"networkidle" }.
      // The safe-dirs + magic-byte + size-cap checks below still apply to the
      // INLINE HTML content, not to the payload file path itself.
      let fromFilePayload: { html: string; waitUntil?: SetContentWaitUntil } | null = null;
      let filePath: string | undefined;
      let waitUntil: SetContentWaitUntil = 'domcontentloaded';
      for (let i = 0; i < args.length; i++) {
        if (args[i] === '--from-file') {
          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') {

View on GitHub (pinned to 94993f7401)