pbakaus/impeccable · error · Error

config.insertBefore or config.insertAfter (string) required

Error message

config.insertBefore or config.insertAfter (string) required

What it means

Thrown by validateConfig() in live-inject.mjs when neither cfg.insertBefore nor cfg.insertAfter is a string. The injector needs exactly one anchor telling it where in each matched file to insert the live snippet; with no string anchor it has no safe insertion point and refuses to proceed. The check is typeof cfg.insertBefore !== 'string' && typeof cfg.insertAfter !== 'string'.

Source

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

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') {
    throw new Error("config.commentSyntax must be 'html' or 'jsx'");
  }
  if (cfg.cspChecked !== undefined && typeof cfg.cspChecked !== 'boolean') {
    throw new Error("config.cspChecked, if present, must be a boolean");
  }
}

// ---------------------------------------------------------------------------
// Auto-execute
// ---------------------------------------------------------------------------

const _running = process.argv[1];
if (_running?.endsWith('live-inject.mjs') || _running?.endsWith('live-inject.mjs/')) {
  enterLiveRoot();
  injectCli();
}

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Add exactly one anchor as a string: 'insertAfter' set to a literal HTML/JSX fragment like "<head>" or 'insertBefore' set to "</body>".
  2. If you set both, prefer keeping the single correct one and remove the other to avoid ambiguity (validateConfig allows both being strings but downstream logic prefers one).
  3. Confirm the anchor string actually appears in the target files, otherwise injection silently no-ops.

Example fix

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

Strategy: validation

Validate before calling

const anchor = typeof cfg.insertAfter === 'string' ? cfg.insertAfter
  : typeof cfg.insertBefore === 'string' ? cfg.insertBefore
  : null;
if (!anchor) throw new Error('config needs insertAfter or insertBefore as a string');

Type guard

function hasStringAnchor(cfg: any): cfg is { insertAfter: string } | { insertBefore: string } {
  return typeof cfg?.insertAfter === 'string' || typeof cfg?.insertBefore === 'string';
}

Prevention

When it happens

Trigger: validateConfig runs on a config missing both 'insertBefore' and 'insertAfter', or where both are set to non-string values (null, undefined, number). Example: {files:[...], commentSyntax:'html'} with no anchor, or insertAfter: null.

Common situations: User copies a partial config template that omitted the anchor; renames the key (e.g. 'insertAfterHead') and forgets to update it; sets insertBefore to a RegExp object instead of its source string; deletes the anchor thinking the injector has a default (it does not).

Related errors


AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13). Data as JSON: /api/errors/7b782715d73ba754. Report an issue: GitHub.