{"record":{"id":"7cca7bc79b5660a2","repo":"can1357/oh-my-pi","slug":"commit-summary-cannot-be-empty","errorCode":null,"errorMessage":"Commit summary cannot be empty","messagePattern":"Commit summary cannot be empty","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/commit/conventional/commit-types.ts","lineNumber":155,"sourceCode":"\t\tscope: coerceOptionalScope(input.scope),\n\t\tsummary: typeof input.summary === \"string\" ? input.summary : undefined,\n\t\tdetails: normalizeDetails(input.details),\n\t\tissueRefs: stringsFrom(input.issueRefs),\n\t};\n}\n\n/** Build a normalized conventional commit value. */\nexport function conventionalCommit(input: {\n\ttype: string;\n\tscope?: unknown;\n\tsummary: string;\n\tbody?: readonly string[];\n\tfooters?: readonly string[];\n}): ConventionalCommit {\n\tconst type = canonicalCommitType(input.type);\n\tif (!type) throw new Error(`Invalid commit type: ${input.type}`);\n\tconst scope = coerceOptionalScope(input.scope);\n\tif (!input.summary.trim()) throw new Error(\"Commit summary cannot be empty\");\n\treturn {\n\t\ttype,\n\t\tscope,\n\t\tsummary: input.summary,\n\t\tbody: [...(input.body ?? [])],\n\t\tfooters: [...(input.footers ?? [])],\n\t};\n}\n\nfunction sanitizeScopeSegment(segment: string): string | null {\n\tconst out: string[] = [];\n\tlet lastWasSeparator = false;\n\tfor (const char of segment.trim()) {\n\t\tif (/^[a-z0-9]$/.test(char)) {\n\t\t\tout.push(char);\n\t\t\tlastWasSeparator = false;\n\t\t} else if (char === \"-\" || char === \"_\") {\n\t\t\tif (out.length > 0 && !lastWasSeparator) {","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/commit/conventional/commit-types.ts#L137-L173","documentation":"conventionalCommit() requires a non-empty summary: after type validation, it trims the summary and throws if nothing remains. A conventional commit without a subject line is invalid, so construction is refused rather than emitting an empty commit message.","triggerScenarios":"Calling conventionalCommit (directly or via generateConventionalCommit/messageFromAnalysis/parseFastCommitMarkdown) with summary \"\", whitespace, or undefined coerced to an empty string — typically when model output omitted the summary line or a parser produced an empty capture.","commonSituations":"LLM response truncated before the summary was generated; markdown parse where the heading line is missing; programmatic use passing summary from an optional field without a default.","solutions":["Provide a non-empty summary string before constructing the commit","Re-run generation — truncation (stopReason length) often causes missing summaries","If parsing markdown, ensure the summary/heading line is present in the source","Add a fallback summary (e.g. derived from the file changes) when input is optional"],"exampleFix":"// before\nconventionalCommit({ type: \"fix\", summary: detail.trim() ?? \"\" })\n// after\nconst summary = detail.trim() || \"update affected modules\";\nconventionalCommit({ type: \"fix\", summary });","handlingStrategy":"validation","validationCode":"const summary = (input.summary ?? \"\").trim();\nif (!summary) throw new Error(\"Summary required before building commit\");","typeGuard":"function hasSummary(s: unknown): s is string {\n  return typeof s === \"string\" && s.trim().length > 0;\n}","tryCatchPattern":"try {\n  const commit = conventionalCommit({ ...parsed, summary: parsed.summary });\n} catch (err) {\n  if (err.message === \"Commit summary cannot be empty\") {\n    // regenerate or derive a summary from the diff before retrying\n  } else throw err;\n}","preventionTips":["Check model stopReason — truncated responses often lack the summary","Default missing summaries from diff content instead of passing empty strings","Trim and validate summaries at the parse boundary"],"tags":["conventional-commits","validation","empty-field"],"backgroundTag":"empty-commit-summary","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}