garrytan/gstack · error · Error

pdf: --from-file ${payloadPath} must be a JSON object, got $

Error message

pdf: --from-file ${payloadPath} must be a JSON object, got ${Array.isArray(json) ? 'array' : typeof json}.

What it means

Thrown by parsePdfFromFile() when the parsed JSON is not a plain object — it is null, an array, or a primitive type (string, number, boolean). The PDF config must be a JSON object whose keys map to ParsedPdfArgs fields (output, format, width, etc.).

Source

Thrown at browse/src/meta-commands.ts:161

  // must pass validateReadPath so the safe-dirs policy can't be skirted
  // by routing reads through the --from-file shortcut.
  try {
    validateReadPath(path.resolve(payloadPath));
  } catch {
    throw new Error(
      `pdf: --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 (err) {
    const msg = err instanceof Error ? err.message : String(err);
    throw new Error(`pdf: --from-file ${payloadPath} is not valid JSON (${msg}).`);
  }
  if (json === null || typeof json !== 'object' || Array.isArray(json)) {
    throw new Error(`pdf: --from-file ${payloadPath} must be a JSON object, got ${Array.isArray(json) ? 'array' : typeof json}.`);
  }
  const out: ParsedPdfArgs = {
    output: json.output || `${TEMP_DIR}/browse-page.pdf`,
    format: json.format,
    width: json.width,
    height: json.height,
    marginTop: json.marginTop,
    marginRight: json.marginRight,
    marginBottom: json.marginBottom,
    marginLeft: json.marginLeft,
    headerTemplate: json.headerTemplate,
    footerTemplate: json.footerTemplate,
    pageNumbers: json.pageNumbers === true,
    tagged: json.tagged === true,
    outline: json.outline === true,
    printBackground: json.printBackground === true,
    preferCSSPageSize: json.preferCSSPageSize === true,
    toc: json.toc === true,

View on GitHub (pinned to 94993f7401)

Solutions

  1. Ensure the JSON file's top-level value is an object ({...}), not an array ([...]) or primitive
  2. Wrap any array content inside an object if the config needs list data

Example fix

// before — array at top level
[
  { "format": "A4" },
  { "format": "Letter" }
]

// after — single object
{
  "format": "A4",
  "printBackground": true
}
Defensive patterns

Strategy: type-guard

Validate before calling

const raw = fs.readFileSync(payloadPath, 'utf8');
const json = JSON.parse(raw);
if (json === null || typeof json !== 'object' || Array.isArray(json)) {
  throw new Error('Payload must be a JSON object');
}

Type guard

function isPdfConfigObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: The --from-file JSON file parses successfully but the top-level value is an array (e.g., '[...]'), null, or a primitive like a bare string or number.

Common situations: User wraps the config in an array instead of an object, or the file contains a bare JSON value from a different tool's output format.

Related errors


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