can1357/oh-my-pi · error · Error

Markdown analysis type(scope): summary heading not found

Error message

Markdown analysis type(scope): summary heading not found

What it means

parseConventionalAnalysisMarkdown expects the model's analysis to start with a 'type(scope): summary' heading line. It tries strict heading parsing, then coerces the first markdown '#' line. If no valid heading exists anywhere in the text, this error is thrown (raised by parseConventionalAnalysisMarkdown, called by generateDirectAnalysis, runMapReduce, analysis, canonical).

Source

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

	if (isRecord(payload)) return analysisFromMapping(payload, defaultType);
	const lines = cleanMarkdownText(text).split(/\r?\n/);
	let heading: { index: number; type: CommitType; scope: string | null; summary: string } | undefined;
	let coerced: typeof heading;
	for (let index = 0; index < Math.min(5, lines.length); index += 1) {
		const line = lines[index] ?? "";
		const candidate = stripHeadingMarkers(line);
		const parsed = parseHeadingLine(candidate, false);
		if (parsed) {
			heading = { index, ...parsed };
			break;
		}
		if (!coerced && line.trim().startsWith("#")) {
			const fallback = parseHeadingLine(candidate, true);
			if (fallback) coerced = { index, ...fallback };
		}
	}
	heading ??= coerced;
	if (!heading) throw new Error("Markdown analysis type(scope): summary heading not found");
	const detailTexts: string[] = [];
	const issueRefs: string[] = [];
	for (const line of lines.slice(heading.index + 1)) {
		const stripped = line.trim();
		if (!stripped) continue;
		const lower = stripped.toLowerCase();
		if (lower.startsWith("fixes:") || lower.startsWith("closes:") || lower.startsWith("resolves:")) {
			const separator = stripped.indexOf(":");
			for (const ref of stripped.slice(separator + 1).split(",")) if (ref.trim()) issueRefs.push(ref.trim());
			continue;
		}
		const bullet = stripBullet(stripped);
		if (!bullet) continue;
		detailTexts.push(ensureSentence(bullet));
		issueRefs.push(...(bullet.match(ISSUE_RE) ?? []));
	}
	return conventionalAnalysis({
		type: heading.type,

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry or switch to a model that reliably follows the type(scope): heading format
  2. Strengthen the reduce prompt's format instructions / add a format example
  3. Verify the raw text isn't wrapped in tags/fences the parser doesn't strip; strip them before parsing
  4. Add leniency: coerce the first non-empty line into the heading if it looks like a conventional commit

Example fix

// before
const analysis = parseConventionalAnalysisMarkdown(raw);
// after: pre-strip wrappers
const cleaned = extractTagLenient(raw, "analysis") ?? raw;
const analysis = parseConventionalAnalysisMarkdown(cleaned);
Defensive patterns

Strategy: try-catch

Validate before calling

const headingRe = /^#{0,3}\s*\w+(\([^)]*\))?!?:\s.+$/m;
if (!headingRe.test(raw)) throw new Error("Model output missing type(scope): heading");

Try / catch

try {
  const analysis = parseConventionalAnalysisMarkdown(raw);
} catch (err) {
  if (err instanceof Error && err.message.includes("heading not found")) {
    // regenerate or use a lenient fallback parser
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parseConventionalAnalysisMarkdown on model output that lacks any 'type(scope): subject' line and has no coercible '#' heading — the model replied with prose, JSON, or wrapped the heading in a code fence or label that defeats the parser.

Common situations: Prompt drift after model changes (model ignores the heading format instruction), small/fast models not following the conventional-commit format, output wrapped in <summary> tags that weren't stripped, or non-conventional commit style like plain sentences.

Related errors


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