santifer/career-ops · error · Error

does not contain a YAML mapping — refusing to overwrite it…

Error message

${file} does not contain a YAML mapping — refusing to overwrite it. Nothing was changed.

What it means

parsePluginConfig requires the YAML file to parse to a mapping (object). A file containing a bare scalar (e.g. just 'true') or a list parses cleanly but is not a valid plugin config; spreading it would silently discard the user's data, so the library throws and changes nothing.

Solutions

  1. Convert the top-level structure to a mapping, e.g. plugins: { my-plugin: { enabled: true } }
  2. Remove the stray scalar/list and rebuild the config from config/plugins.example.yml
  3. Check git history (git diff config/plugins.yml) to find what replaced the mapping

Example fix

// before (a list, not a mapping)
- my-plugin
- other-plugin
// Error: config/plugins.yml does not contain a YAML mapping ...
// after
plugins:
  my-plugin:
    enabled: true
  other-plugin:
    enabled: false
Defensive patterns

Strategy: validation

Validate before calling

import { parse } from 'yaml';
const cfg = parse(readFileSync('config/plugins.yml', 'utf8'));
if (cfg === null || typeof cfg !== 'object' || Array.isArray(cfg)) throw new Error('plugins.yml must be a YAML mapping at the top level');

Type guard

const isMapping = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);

Try / catch

try { await pluginsCmd.disable('my-plugin'); } catch (e) { if (e.message.includes('does not contain a YAML mapping')) { console.error(e.message); /* ask user to restructure or restore from example */ } throw e; }

Prevention

When it happens

Trigger: Calling the plugin enable/disable path when config/plugins.yml contains a top-level YAML array or scalar instead of a key/value mapping.

Common situations: Rewriting plugins.yml as a simple list of names; a script that serialized an array into the file; pasting a list-format example from outdated docs.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/777e9a1ef01d619f. Report an issue: GitHub.

Appendix: source

Thrown at plugins.mjs:252

  const hasContent = raw.split('\n').some((line) => {
    const t = line.trim();
    return t !== '' && !t.startsWith('#');
  });
  if (!hasContent) return {};
  let cfg;
  try {
    cfg = yaml.load(raw);
  } catch (err) {
    throw new Error(
      `${file} is not valid YAML (${String(err.message).split('\n')[0]}) — refusing to overwrite it. `
      + 'Fix the file and re-run; nothing was changed.',
    );
  }
  if (cfg == null) return {};   // empty file: same as absent
  // A scalar or a list parses cleanly and is still not a config. Spreading one
  // below would discard it just as silently as the empty object did.
  if (typeof cfg !== 'object' || Array.isArray(cfg)) {
    throw new Error(`${file} does not contain a YAML mapping — refusing to overwrite it. Nothing was changed.`);
  }
  return cfg;
}

// Write enabled:true/false into config/plugins.yml, merging (never clobbering
// the user's other plugins or non-secret settings).
function setEnabled(id, on, settings) {
  const file = path.join(ROOT, 'config', 'plugins.yml');
  const cfg = parsePluginConfig(existsSync(file) ? readFileSync(file, 'utf8') : null, file);
  if (!cfg.plugins || typeof cfg.plugins !== 'object') cfg.plugins = {};
  const prev = (cfg.plugins[id] && typeof cfg.plugins[id] === 'object') ? cfg.plugins[id] : {};
  cfg.plugins[id] = { ...prev, ...(settings || {}), enabled: on };
  mkdirSync(path.join(ROOT, 'config'), { recursive: true });
  writeFileSync(file, '# career-ops plugin activation — see config/plugins.example.yml\n' + yaml.dump(cfg), 'utf8');
}

// The capability card a user must consent to before a plugin runs.
function capabilityCard(manifest, source) {

View on GitHub (pinned to aac998c7ed)