can1357/oh-my-pi · error · Error

Unknown changelog category: ${raw}

Error message

Unknown changelog category: ${raw}

What it means

normalizeChangelogCategory maps a raw category string (case-insensitively) to the canonical Keep-a-Changelog categories: Added, Changed, Fixed, Deprecated, Removed, Security. Any other value throws. This guards the changelog section generation from model-invented categories.

Source

Thrown at packages/coding-agent/src/commit/conventional/markdown.ts:397

function stripCategoryPrefix(text: string): { text: string; category?: ConventionalDetail["changelogCategory"] } {
	const match = text.match(CATEGORY_RE);
	if (!match?.groups) return { text: text.trim() };
	return {
		text: (match.groups.text ?? "").trim(),
		category: changelogCategory(match.groups.bracket ?? match.groups.prefix ?? ""),
	};
}

function changelogCategory(raw: string): ConventionalDetail["changelogCategory"] {
	const normalized = raw.trim().toLowerCase();
	if (normalized === "breaking" || normalized === "breaking changes") return "Breaking Changes";
	if (normalized === "added") return "Added";
	if (normalized === "changed") return "Changed";
	if (normalized === "fixed") return "Fixed";
	if (normalized === "deprecated") return "Deprecated";
	if (normalized === "removed") return "Removed";
	if (normalized === "security") return "Security";
	throw new Error(`Unknown changelog category: ${raw}`);
}

function stripWrappingQuotes(text: string): string {
	const pairs: Record<string, string> = { '"': '"', "'": "'", "`": "`", "“": "”", "‘": "’" };
	const stripped = text.trim();
	return stripped.length >= 2 && pairs[stripped[0] ?? ""] === stripped.at(-1)
		? stripped.slice(1, -1).trim()
		: stripped;
}

function normalizeEscapedWhitespace(text: string): string {
	if (!text.includes("\\")) return text;
	const parts = text.split("`");
	for (let index = 0; index < parts.length; index += 2) {
		parts[index] = (parts[index] ?? "")
			.replaceAll("\\r\\n", "\n")
			.replaceAll("\\n", "\n")
			.replaceAll("\\r", "\n")

View on GitHub (pinned to 9690622007)

Solutions

  1. Constrain the changelog prompt to exactly the six Keep-a-Changelog categories
  2. Add the common alias to the normalization table if it appears frequently in your model's output
  3. Normalize/alias categories before parsing (e.g. 'features'->'added', 'bug fixes'->'fixed')
  4. Regenerate the analysis with a model that follows the changelog format

Example fix

// before: throws on 'Features'
const category = normalizeChangelogCategory("Features");
// after: alias first in prompt or pre-map
const aliases = { features: "added", fixes: "fixed", bugfixes: "fixed" };
const category = normalizeChangelogCategory(aliases[raw.toLowerCase()] ?? raw);
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN = ["added","changed","fixed","deprecated","removed","security"];
if (!KNOWN.includes(raw.trim().toLowerCase())) {
  raw = aliasMap[raw.trim().toLowerCase()] ?? "changed"; // or reject early
}

Try / catch

try {
  category = normalizeChangelogCategory(raw);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Unknown changelog category")) {
    category = "Changed"; // safe bucket for model-invented categories
  } else throw err;
}

Prevention

When it happens

Trigger: Parsing analysis markdown whose detail lines carry a category like 'Features', 'Bug fixes', 'Improvements', 'Performance', or a typo — anything outside the six known categories after normalization reaches the mapper.

Common situations: Model output using non-keep-a-changelog headings ('New', 'Bugs'), localized categories, or prompt examples that list categories not in the canonical set; older model outputs generated under a different changelog prompt.

Related errors


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