garrytan/gstack · error · Error

Invalid JSON in ${filePath}: ${err?.message || err}

Error message

Invalid JSON in ${filePath}: ${err?.message || err}

What it means

Thrown by `browse cookie-import` when `JSON.parse(raw)` throws on the file contents. The original parse error is captured and re-thrown with the file path prepended, preserving the underlying message (e.g. `Unexpected token < in JSON at position 0`). The file is read as UTF-8 text first, so encoding issues also surface here.

Source

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

    case 'cookie-import': {
      const filePath = args[0];
      if (!filePath) throw new Error('Usage: browse cookie-import <json-file>');
      // Path validation — resolve to absolute and check against safe dirs.
      // Fixes #707: relative paths previously bypassed the safe directory check.
      // Mirrors validateOutputPath() — resolves symlinks (e.g., macOS /tmp → /private/tmp).
      const resolved = path.resolve(filePath);
      let resolvedReal = resolved;
      try { resolvedReal = fs.realpathSync(resolved); } catch {
        // File may not exist yet — resolve parent dir instead
        try { resolvedReal = path.join(fs.realpathSync(path.dirname(resolved)), path.basename(resolved)); } catch {}
      }
      if (!SAFE_DIRECTORIES.some(dir => isPathWithin(resolvedReal, dir))) {
        throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
      }
      if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
      const raw = fs.readFileSync(filePath, 'utf-8');
      let cookies: any[];
      try { cookies = JSON.parse(raw); } catch (err: any) { throw new Error(`Invalid JSON in ${filePath}: ${err?.message || err}`); }
      if (!Array.isArray(cookies)) throw new Error('Cookie file must contain a JSON array');

      // Auto-fill domain from current page URL when missing (consistent with cookie command)
      const pageUrl = new URL(page.url());
      const defaultDomain = pageUrl.hostname;

      for (const c of cookies) {
        if (!c.name || c.value === undefined) throw new Error('Each cookie must have "name" and "value" fields');
        if (!c.domain) {
          c.domain = defaultDomain;
        } else {
          const cookieDomain = c.domain.startsWith('.') ? c.domain.slice(1) : c.domain;
          if (cookieDomain !== defaultDomain && !defaultDomain.endsWith('.' + cookieDomain)) {
            throw new Error(`Cookie domain "${c.domain}" does not match current page domain "${defaultDomain}". Use the target site first.`);
          }
        }
        if (!c.path) c.path = '/';
      }

View on GitHub (pinned to 94993f7401)

Solutions

  1. Validate the file parses as JSON before importing: `JSON.parse(fs.readFileSync(fp, 'utf-8'))` in a scratch script.
  2. If the file is Netscape format, convert it to a JSON array of cookie objects first.
  3. Strip a leading BOM: `raw.charCodeAt(0) === 0xFEFF` then `raw.slice(1)`.
  4. Re-export the cookies with a tool that emits JSON (Playwright's `context.cookies()` output is the expected shape).

Example fix

// before
// file contains: name=value; name2=value2  (NOT JSON)
await runBrowseCommand(['cookie-import', fp]);

// after
// write a proper JSON array
const cookies = [{ name: 'name', value: 'value', domain: 'example.com', path: '/' }];
fs.writeFileSync(fp, JSON.stringify(cookies));
await runBrowseCommand(['cookie-import', fp]);
Defensive patterns

Strategy: try-catch

Validate before calling

import fs from 'fs';
function validateCookieJsonFile(filePath: string): any[] {
  let raw = fs.readFileSync(filePath, 'utf-8');
  if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1); // strip BOM
  const parsed = JSON.parse(raw); // throws on invalid
  if (!Array.isArray(parsed)) throw new Error('cookie file must contain a JSON array');
  return parsed;
}

Type guard

function isCookieArray(v: unknown): v is unknown[] {
  return Array.isArray(v);
}

Try / catch

try {
  const cookies = JSON.parse(raw);
} catch (err: any) {
  throw new Error(`Invalid JSON in ${filePath}: ${err?.message || err}`);
}

Prevention

When it happens

Trigger: The file is actually HTML (a login page saved by mistake), a Netscape-format `cookies.txt` (not JSON), a JSON file with a trailing comma, a BOM-prefixed file, a file that was concatenated with a second JSON document, or a binary/empty file.

Common situations: User ran a `curl` that saved an error page instead of the cookie JSON; an export tool wrote Netscape format instead of JSON; a hand-edited JSON file has a trailing comma or single quotes; the file was corrupted by a partial write during a crash.

Understand the failure class

Related errors


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