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
- Add a `## [Unreleased]` section to the top of the changelog (after any title/intro)
- Match the expected heading format exactly: `## [Unreleased]` with brackets at heading level 2
- Verify changelogTargets points at the intended changelog file
- 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
- Always keep a `## [Unreleased]` heading (exact spelling, brackets, level 2) at the top of CHANGELOG.md
- Never rename or re-level the Unreleased heading with formatters
- Verify changelogTargets paths point at real changelog files
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
- Unknown changelog category: ${raw}
- Replacement text is not valid UTF-8: {err}
- V2 compaction stream parse failed: ${err instanceof Error ?
- ${source} response is missing credentials.
- auto-thinking: unparseable local classification: ${JSON.stri
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6b2f6bd6c2ef8e45.
Report an issue: GitHub.