can1357/oh-my-pi · error · Error

No [Unreleased] section found in changelog

Error message

No [Unreleased] section found in changelog

What it means

parseUnreleasedSection() scans changelog content line-by-line for a heading matching UNRELEASED_PATTERN (`## [Unreleased]`-style). If no line matches, it throws because all downstream logic (unreleased, runChangelogFlow, applyChangelogProposals) requires an existing [Unreleased] section to append entries to.

Source

Thrown at packages/coding-agent/src/commit/changelog/parse.ts:10

import type { UnreleasedSection } from "../../commit/types";

const UNRELEASED_PATTERN = /^##\s+\[?Unreleased\]?/i;
const SECTION_PATTERN = /^###\s+(.*)$/;

export function parseUnreleasedSection(content: string): UnreleasedSection {
	const lines = content.split("\n");
	const startIndex = lines.findIndex(line => UNRELEASED_PATTERN.test(line.trim()));
	if (startIndex === -1) {
		throw new Error("No [Unreleased] section found in changelog");
	}

	let endIndex = lines.length;
	for (let i = startIndex + 1; i < lines.length; i += 1) {
		if (lines[i]?.startsWith("## ")) {
			endIndex = i;
			break;
		}
	}

	const sectionLines = lines.slice(startIndex + 1, endIndex);
	const entries: Record<string, string[]> = {};
	let currentSection: string | null = null;
	for (const line of sectionLines) {
		const sectionMatch = line.match(SECTION_PATTERN);
		if (sectionMatch) {
			currentSection = sectionMatch[1]?.trim() || null;
			if (currentSection) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Add a `## [Unreleased]` section to the top of the changelog (after any title/intro)
  2. Match the expected heading format exactly: `## [Unreleased]` with brackets at heading level 2
  3. Verify changelogTargets points at the intended changelog file
  4. Run `bun run release` once to generate a normalized changelog skeleton

Example fix

<!-- before -->
# Changelog
## 1.0.0 - 2026-01-01
...
<!-- after -->
# Changelog
## [Unreleased]
## 1.0.0 - 2026-01-01
...
Defensive patterns

Strategy: validation

Validate before calling

const content = await Bun.file("CHANGELOG.md").text();
if (!/^##\s+\[Unreleased\]/m.test(content)) {
  throw new Error("CHANGELOG.md needs a '## [Unreleased]' section");
}

Try / catch

try {
  const section = parseUnreleasedSection(content);
} catch (err) {
  if (err.message.includes("No [Unreleased] section")) {
    content = "## [Unreleased]\n\n" + content; // or prompt the user to add it
  } else throw err;
}

Prevention

When it happens

Trigger: Calling any changelog flow against a CHANGELOG.md that lacks a `## [Unreleased]` heading — fresh repos, renamed headings (e.g. `## Unreleased` without brackets, wrong heading level), or pointing at the wrong changelog file.

Common situations: New project whose CHANGELOG.md has only released sections; heading reformatted by a tool or by hand (`# [Unreleased]`, `## UNRELEASED`, missing brackets); changelogTargets misconfigured to a file without the section.

Related errors


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