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
- Validate the file parses as JSON before importing: `JSON.parse(fs.readFileSync(fp, 'utf-8'))` in a scratch script.
- If the file is Netscape format, convert it to a JSON array of cookie objects first.
- Strip a leading BOM: `raw.charCodeAt(0) === 0xFEFF` then `raw.slice(1)`.
- 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
- Validate the file parses as JSON in a scratch step before importing.
- Strip a leading UTF-8 BOM before parsing.
- Use Playwright context.cookies() export format as the canonical shape.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Usage: browse cookie-import <json-file>
- File not found: ${filePath}
- Each cookie must have "name" and "value" fields
- Cookie domain "${c.domain}" does not match current page doma
- --domain "${domain}" does not match current page domain "${p
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/6de008ca2470e7e9.
Report an issue: GitHub.