santifer/career-ops · error · Error

wttj: /api/env payload is not valid JSON

Error message

wttj: /api/env payload is not valid JSON

What it means

A {...} span was found in /api/env but JSON.parse rejected it. The brace-matching is greedy (first { to last }), so any stray brace elsewhere on the page (inline JS, analytics) can make the span invalid JSON.

Source

Thrown at providers/wttj.mjs:61

  }
  return url;
}

/**
 * Parse the `window.env = {...}` payload served by /api/env and extract the
 * Algolia application id + client search key.
 * @param {string} text
 * @returns {{ appId: string, apiKey: string }}
 */
export function parseEnvPayload(text) {
  const start = text.indexOf('{');
  const end = text.lastIndexOf('}');
  if (start === -1 || end <= start) throw new Error('wttj: /api/env payload has no JSON object');
  let env;
  try {
    env = JSON.parse(text.slice(start, end + 1));
  } catch {
    throw new Error('wttj: /api/env payload is not valid JSON');
  }
  const appId = typeof env.PUBLIC_ALGOLIA_APPLICATION_ID === 'string' ? env.PUBLIC_ALGOLIA_APPLICATION_ID.trim() : '';
  const apiKey = typeof env.PUBLIC_ALGOLIA_API_KEY_CLIENT === 'string' ? env.PUBLIC_ALGOLIA_API_KEY_CLIENT.trim() : '';
  // App ids are short alphanumerics; validating keeps the derived Algolia
  // hostname from being attacker-shaped if the env payload ever changes.
  if (!/^[A-Z0-9]{6,16}$/i.test(appId)) throw new Error(`wttj: unexpected Algolia app id "${appId}"`);
  // The key is only ever sent as a request header (never used to build a
  // host), so don't over-constrain its format — WTTJ may rotate to a longer
  // or non-hex (e.g. secured/base64) client key. Length bounds only.
  if (!apiKey || apiKey.length < 16 || apiKey.length > 500) {
    throw new Error('wttj: unexpected Algolia api key shape');
  }
  return { appId, apiKey };
}

/**
 * Normalize a single Algolia hit. Exported for tests.
 *

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Inspect the raw /api/env body and locate the real window.env = {...} block.
  2. Tighten extraction to the window.env assignment instead of greedy first-brace-to-last-brace.
  3. If WTTJ now emits a JS object literal (unquoted keys, trailing commas), switch to a tolerant parser or a brace-balanced extractor.

Example fix

// before — greedy slice can capture stray braces
const start = text.indexOf("{");
const end = text.lastIndexOf("}");
env = JSON.parse(text.slice(start, end + 1));
// after — scope to window.env and balance braces
const assign = text.indexOf("window.env");
const firstBrace = text.indexOf("{", assign);
const obj = balanceBraces(text, firstBrace); // returns the matched {...}
env = JSON.parse(obj);
Defensive patterns

Strategy: try-catch

Validate before calling

const slice = envText.slice(envText.indexOf("{"), envText.lastIndexOf("}") + 1);
try { JSON.parse(slice); }
catch { console.warn("wttj env slice is not strict JSON — extraction may need updating"); }

Try / catch

try { parseEnvPayload(envText); }
catch (err) {
  if (/not valid JSON/.test(err.message)) { logUpstreamChange("wttj", err.message); }
  throw err;
}

Prevention

When it happens

Trigger: The page has additional {/} outside the env object so the greedy slice captures invalid JSON; WTTJ added inline scripts with brace characters; the env object itself now contains unquoted keys or trailing commas that strict JSON rejects; the response was truncated mid-object.

Common situations: WTTJ added inline JS with braces; a partial response truncating the object; a CDN injecting a script tag with braces.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/83fcc3f1d7601e9d. Report an issue: GitHub.