can1357/oh-my-pi · error · Error

Config overlay must be a YAML mapping: ${filePath}

Error message

Config overlay must be a YAML mapping: ${filePath}

What it means

After successful YAML parsing, Settings requires the overlay document to be a YAML mapping (an object). If the document parses to an array, a scalar, or any non-object value, the loader refuses it because overlays are merged as key/value maps. This keeps partial-application semantics well-defined.

Source

Thrown at packages/coding-agent/src/config/settings.ts:1601

		let content: string;
		try {
			content = await Bun.file(filePath).text();
		} catch (error) {
			throw new Error(
				isEnoent(error)
					? `Config overlay not found: ${filePath}`
					: `Failed to read config overlay ${filePath}: ${String(error)}`,
			);
		}
		let parsed: unknown;
		try {
			parsed = YAML.parse(content);
		} catch (error) {
			throw new Error(`Failed to parse config overlay ${filePath}: ${String(error)}`);
		}
		if (parsed === null || parsed === undefined) return {};
		if (typeof parsed !== "object" || Array.isArray(parsed)) {
			throw new Error(`Config overlay must be a YAML mapping: ${filePath}`);
		}
		return this.#migrateRawSettings(parsed as RawSettings, captureLegacyChangelogVersion);
	}

	async #migrateFromLegacy(): Promise<void> {
		if (!this.#configPath) return;

		let settings: RawSettings = {};
		let migrated = false;

		// 1. Migrate from settings.json
		const settingsJsonPath = path.join(this.#agentDir, "settings.json");
		try {
			const parsed: unknown = JSONC.parse(await Bun.file(settingsJsonPath).text());
			if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
				settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed as RawSettings));
				migrated = true;
				try {

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap the overlay content in a top-level mapping: `key:\n subkey: value` instead of a list or bare value.
  2. If the file should be empty, empty it entirely (null/empty documents are tolerated and treated as {}).
  3. Check that you didn't lose the top-level keys when editing — move list items under a mapping key.

Example fix

// before (parses as an array)
- model: gpt-5
- agent: build
// after
overrides:
  model: gpt-5
  agent: build
Defensive patterns

Strategy: validation

Validate before calling

const doc = YAML.parse(await readFile(overlayPath, "utf8"));
if (doc !== null && (typeof doc !== "object" || Array.isArray(doc))) {
  throw new Error(`${overlayPath} must contain a top-level YAML mapping`);
}

Type guard

function isMapping(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: An overlay file whose top level is a YAML list (e.g. `- a\n- b`), a bare string/number, or a document that resolves to only a scalar; also YAML files containing just a comment or `---` with no mapping.

Common situations: Someone pasted an array of settings entries instead of an object; a user wrote `settings: ...` content at the wrong nesting level leaving only a scalar; an empty file plus a stray `-` list item.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/83c861ba832847b3. Report an issue: GitHub.