pbakaus/impeccable · error · Error

config.json must be an object

Error message

config.json must be an object

What it means

validateConfig() in skill/scripts/live-inject.mjs guards every inject run: the parsed content of .impeccable/live/config.json must be a plain object before any other field is checked. JSON that parses to null, an array, a string, or a number fails this first check. The file drives script injection into the listed files, so a non-object root means the config was authored as the wrong kind of JSON document entirely.

Solutions

  1. Rewrite .impeccable/live/config.json as an object containing the required keys: files (non-empty string array), insertBefore or insertAfter (string), commentSyntax ('html'|'jsx')
  2. Run `node skill/scripts/live-inject.mjs --check` first to see the current content
  3. Delete the file and re-run the Setup/live flow — it is created once per project when missing

Example fix

// .impeccable/live/config.json — before
["src/**/*.html"]

// after
{
  "files": ["src/**/*.html"],
  "insertAfter": "</title>",
  "commentSyntax": "html"
}
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';

const raw = fs.readFileSync('.impeccable/live/config.json', 'utf-8');
const cfg = JSON.parse(raw); // throws first on broken JSON
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) {
  throw new TypeError('live inject config root must be a JSON object');
}

Type guard

const isInjectConfigObject = (cfg) =>
  !!cfg && typeof cfg === 'object' && !Array.isArray(cfg);

Prevention

When it happens

Trigger: Running live-inject (directly or via the live flow that reads .impeccable/live/config.json) when the file's root is e.g. ["src/**/*.html"], "config", 1, or null. The `--check` flag prints whether the file exists and its content, confirming the shape.

Common situations: An agent hand-authoring the config on first live run emits the files array as the whole document; the file is truncated to a fragment by a partial write; a template placeholder string is left as the root.

Related errors


AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18). Data as JSON: /api/errors/2cc1f072b44d29f5. Report an issue: GitHub.

Appendix: source

Thrown at skill/scripts/live-inject.mjs:450

      re += '[^/]';
      i += 1;
    } else if (/[.+^${}()|[\]\\]/.test(c)) {
      re += '\\' + c;
      i += 1;
    } else {
      re += c;
      i += 1;
    }
  }
  return new RegExp('^' + re + '$');
}

// ---------------------------------------------------------------------------
// Core operations
// ---------------------------------------------------------------------------

function validateConfig(cfg) {
  if (!cfg || typeof cfg !== 'object') throw new Error('config.json must be an object');
  if (!Array.isArray(cfg.files) || cfg.files.length === 0) {
    throw new Error('config.files (non-empty string array) required');
  }
  if (!cfg.files.every((f) => typeof f === 'string' && f.length > 0)) {
    throw new Error('config.files must contain only non-empty strings');
  }
  if (cfg.exclude !== undefined) {
    if (!Array.isArray(cfg.exclude)) {
      throw new Error('config.exclude, if present, must be a string array');
    }
    if (!cfg.exclude.every((f) => typeof f === 'string' && f.length > 0)) {
      throw new Error('config.exclude must contain only non-empty strings');
    }
  }
  if (typeof cfg.insertBefore !== 'string' && typeof cfg.insertAfter !== 'string') {
    throw new Error('config.insertBefore or config.insertAfter (string) required');
  }
  if (cfg.commentSyntax !== 'html' && cfg.commentSyntax !== 'jsx') {

View on GitHub (pinned to f88b2837a7)