{"record":{"id":"6b2f6bd6c2ef8e45","repo":"can1357/oh-my-pi","slug":"no-unreleased-section-found-in-changelog","errorCode":null,"errorMessage":"No [Unreleased] section found in changelog","messagePattern":"No \\[Unreleased\\] section found in changelog","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/commit/changelog/parse.ts","lineNumber":10,"sourceCode":"import type { UnreleasedSection } from \"../../commit/types\";\n\nconst UNRELEASED_PATTERN = /^##\\s+\\[?Unreleased\\]?/i;\nconst SECTION_PATTERN = /^###\\s+(.*)$/;\n\nexport function parseUnreleasedSection(content: string): UnreleasedSection {\n\tconst lines = content.split(\"\\n\");\n\tconst startIndex = lines.findIndex(line => UNRELEASED_PATTERN.test(line.trim()));\n\tif (startIndex === -1) {\n\t\tthrow new Error(\"No [Unreleased] section found in changelog\");\n\t}\n\n\tlet endIndex = lines.length;\n\tfor (let i = startIndex + 1; i < lines.length; i += 1) {\n\t\tif (lines[i]?.startsWith(\"## \")) {\n\t\t\tendIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tconst sectionLines = lines.slice(startIndex + 1, endIndex);\n\tconst entries: Record<string, string[]> = {};\n\tlet currentSection: string | null = null;\n\tfor (const line of sectionLines) {\n\t\tconst sectionMatch = line.match(SECTION_PATTERN);\n\t\tif (sectionMatch) {\n\t\t\tcurrentSection = sectionMatch[1]?.trim() || null;\n\t\t\tif (currentSection) {","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/commit/changelog/parse.ts#L1-L28","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"<!-- before -->\n# Changelog\n## 1.0.0 - 2026-01-01\n...\n<!-- after -->\n# Changelog\n## [Unreleased]\n## 1.0.0 - 2026-01-01\n...","handlingStrategy":"validation","validationCode":"const content = await Bun.file(\"CHANGELOG.md\").text();\nif (!/^##\\s+\\[Unreleased\\]/m.test(content)) {\n  throw new Error(\"CHANGELOG.md needs a '## [Unreleased]' section\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  const section = parseUnreleasedSection(content);\n} catch (err) {\n  if (err.message.includes(\"No [Unreleased] section\")) {\n    content = \"## [Unreleased]\\n\\n\" + content; // or prompt the user to add it\n  } else throw err;\n}","preventionTips":["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"],"tags":["changelog","parsing","missing-section"],"backgroundTag":"missing-changelog-section","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}