zen-browser/desktop · warning · Error

Mods data file is invalid

Error message

Mods data file is invalid

What it means

Thrown by nsZenMods.getMods() after IOUtils.readJSON(this.modsDataFile) successfully parses <profile>/zen-themes.json but the resulting value is null or not an object (e.g. an array, string, number, or boolean). The provider's contract is that zen-themes.json holds a map of modId -> mod record, so a non-object top level is treated as corruption. The throw is control flow: it jumps into the immediately-following catch block (ZenMods.mjs:437) which writes {} back to the file and surfaces the toast "zen-themes-corrupted" (timeout 8000ms). Because the throw is caught locally, callers of getMods() never see an exception — but note the local `mods` variable retains the invalid value and is returned at line 448, a latent quirk worth knowing about.

Source

Thrown at src/zen/mods/ZenMods.mjs:435

  getModFolder(modId) {
    return PathUtils.join(this.modsRootPath, modId);
  }

  async getMods() {
    if (!(await IOUtils.exists(this.modsDataFile))) {
      await IOUtils.writeJSON(this.modsDataFile, {});

      return {};
    }

    let mods = {};

    try {
      mods = await IOUtils.readJSON(this.modsDataFile);

      if (mods === null || typeof mods !== "object") {
        throw new Error("Mods data file is invalid");
      }
    } catch {
      // If we have a corrupted file, reset it
      await IOUtils.writeJSON(this.modsDataFile, {});

      Services.wm
        .getMostRecentWindow("navigator:browser")
        .gZenUIManager.showToast("zen-themes-corrupted", {
          timeout: 8000,
        });
    }

    return mods;
  }

  async getModPreferences(mod) {
    const modPath = PathUtils.join(
      this.modsRootPath,

View on GitHub (pinned to 89e31cd31f)

Solutions

  1. Acknowledge the self-heal: the catch already rewrites zen-themes.json to {} and shows the "zen-themes-corrupted" toast. Dismiss the toast and re-enable mods from the Zen mods preferences; no manual file work is required.
  2. If you want to recover the previous mod list, close Zen, restore <profile>/zen-themes.json from backup BEFORE next launch (the catch resets it on every getMods() call that sees the bad shape), then reopen Zen.
  3. If the corruption recurs, identify the writer: check whether a profile-sync, dotfiles, or cloud-drive integration is overwriting zen-themes.json, and exclude the Zen profile's zen-themes.json from sync.
  4. To start clean without the toast, shut Zen down and delete <profile>/zen-themes.json — the exists() guard at line 423 will recreate it as {} on next getMods() without firing the corruption toast.

Example fix

// before: throw on null/non-object, but `mods` keeps the bad value and is returned
mods = await IOUtils.readJSON(this.modsDataFile);
if (mods === null || typeof mods !== "object") {
  throw new Error("Mods data file is invalid");
}
// ... catch resets the file on disk but does NOT reset `mods`
return mods;

// after: reset `mods` too, and use a stricter guard that rejects arrays
mods = await IOUtils.readJSON(this.modsDataFile);
if (
  mods === null ||
  typeof mods !== "object" ||
  Array.isArray(mods)
) {
  await IOUtils.writeJSON(this.modsDataFile, {});
  mods = {};                       // ensure returned value is valid
  Services.wm
    .getMostRecentWindow("navigator:browser")
    ?.gZenUIManager?.showToast("zen-themes-corrupted", { timeout: 8000 });
}
return mods;
Defensive patterns

Strategy: type-guard

Validate before calling

// Run before relying on the mods map; avoids the throw entirely by validating
// shape and falling back to {} when the file is wrong.
async function safeReadMods(modsDataFile) {
  if (!(await IOUtils.exists(modsDataFile))) return {};
  let parsed;
  try {
    parsed = await IOUtils.readJSON(modsDataFile);
  } catch {
    return {};   // invalid JSON syntax — let caller decide on reset/toast
  }
  return isModsMap(parsed) ? parsed : {};
}

// Call instead of the raw IOUtils.readJSON + throw pattern.

Type guard

// Type guard for the Zen mods data file shape: a plain object keyed by modId.
function isModsMap(value) {
  if (value === null || typeof value !== "object") return false;
  if (Array.isArray(value)) return false;
  // every value should itself be a mod record (object)
  for (const v of Object.values(value)) {
    if (v === null || typeof v !== "object" || Array.isArray(v)) {
      return false;
    }
  }
  return true;
}

// Usage:
// const mods = isModsMap(raw) ? raw : {};

Try / catch

// Recommended pattern mirroring the existing local catch, but with `mods`
// reset so callers never receive the invalid value. Use this when wrapping
// getMods() from outside the class.
async function getModsSafe(zenMods) {
  let mods;
  try {
    mods = await zenMods.getMods();
  } catch (e) {
    // getMods already self-heals on disk; just don't propagate
    return {};
  }
  return isModsMap(mods) ? mods : {};
}

Prevention

When it happens

Trigger: zen-themes.json exists (the exists() guard at line 423 passed) AND IOUtils.readJSON succeeds (no SyntaxError, so the bytes are valid JSON) AND the parsed value satisfies `mods === null || typeof mods !== 'object'`. Concretely: file contents are exactly `null`; contents are a JSON array like `["foo"]` (typeof === 'object' but the modId-keyed lookup downstream breaks — though note Array passes the typeof check, only null fails it, so an array would NOT throw here despite being wrong); contents are a quoted string, number, or boolean. The throw fires only for the null / non-object primitives.

Common situations: User or a sync/backup tool (e.g. profile-sync, dotfiles manager) replaced zen-themes.json with `null` or a non-object JSON value. A partial write during a crash left the file containing just `null`. A hand-edit to migrate or inspect mods accidentally saved a scalar. Downgrade from a future schema that used a different top-level shape. Note: a syntactically broken file (truncated/garbled) does NOT trigger this error — it triggers IOUtils.readJSON's own exception, which lands in the same catch via a different path.

Related errors


AI-assisted analysis of zen-browser/desktop@89e31cd31f (2026-08-13). Data as JSON: /api/errors/1bffa58a2f8aaf70. Report an issue: GitHub.