{"record":{"id":"3d856f254d862bcc","repo":"kubernetes/kubernetes","slug":"unexpected-merge-commit-format-q","errorCode":null,"errorMessage":"unexpected merge commit format: %q","messagePattern":"unexpected merge commit format: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hack/apidiff-changelog/main.go","lineNumber":442,"sourceCode":"func outputName(path string) string {\n\tre := regexp.MustCompile(`[^a-zA-Z0-9_-]`)\n\treturn re.ReplaceAllString(path, \"_\") + \".out\"\n}\n\n// getMergeCommitInfo extracts the PR title and description from a GitHub merge commit.\n// The merge commit body is expected in the format produced by GitHub:\n// first line \"Merge pull request #<number> from <branch>\", last line is the PR title.\nfunc getMergeCommitInfo(commit string) (title, description string, err error) {\n\tcmd := exec.Command(\"git\", \"show\", \"--no-patch\", \"--format=%B\", commit)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"git show %s: %w\", commit, err)\n\t}\n\tlines := trimTrailingEmpty(strings.Split(strings.TrimRight(string(out), \"\\n\"), \"\\n\"))\n\t// First line: \"Merge pull request #<number> from <branch>\".\n\twords := strings.Fields(lines[0])\n\tif len(words) < 4 {\n\t\treturn \"\", \"\", fmt.Errorf(\"unexpected merge commit format: %q\", lines[0])\n\t}\n\tprNum := strings.TrimPrefix(words[3], \"#\")\n\ttitle = lines[len(lines)-1]\n\tdescription = fmt.Sprintf(\"See [PR #%s](https://github.com/kubernetes/kubernetes/pull/%s).\", prNum, prNum)\n\treturn title, description, nil\n}\n\n// trimTrailingEmpty removes trailing empty strings from a slice.\nfunc trimTrailingEmpty(lines []string) []string {\n\tfor len(lines) > 0 && lines[len(lines)-1] == \"\" {\n\t\tlines = lines[:len(lines)-1]\n\t}\n\treturn lines\n}\n\n// compareApidiff runs apidiff on two module state files, prints the formatted\n// comparison report to stdout, and returns the incompatible (non-tolerated)\n// changes for changelog verification. Returns an empty string if there are none.","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/kubernetes/kubernetes/blob/b882c60b4023bdf09264c2d5d30a2cadebc240fb/hack/apidiff-changelog/main.go#L424-L460","documentation":"Validation error from getMergeCommitInfo when the first line of the commit body does not match the expected GitHub merge-commit format 'Merge pull request #<n> from <branch>' (main.go:440-442). After trimming, lines[0] is split into fields; fewer than 4 fields triggers this. The %q is the offending first line.","triggerScenarios":"-merge-commit points at a commit whose body's first line is not a GitHub-style merge line. Examples: a squash-merge commit (first line is the PR title), a direct (non-PR) commit, a commit created with a custom merge message, or an empty body.","commonSituations":"Repo uses squash-merges instead of merge-commits (very common in many Kubernetes sub-projects); passing the PR head commit instead of the merge commit; passing a regular commit SHA; GitHub 'Merge branch' style merges.","solutions":["Pass the actual merge commit SHA (the one whose message starts with 'Merge pull request #...').","If the repo only squash-merges, do not use -merge-commit — populate the changelog title/description manually via -update-changelog then edit.","Inspect the commit: `git show --no-patch --format=%B <commit>` and confirm the first line shape."],"exampleFix":"# before (squash-merge commit)\n$ git show --no-patch --format=%B abc123\nAdd DeviceTaintRules API\n# -> first run: unexpected merge commit format: \"Add DeviceTaintRules API\"\n\n# after: use -update-changelog and edit the placeholder instead\n$ ./apidiff-changelog -base main -update-changelog pkg/client","handlingStrategy":"validation","validationCode":"// Confirm the commit body matches GitHub's merge format before passing it in.\nfunc looksLikeGHMerge(body string) bool {\n    words := strings.Fields(strings.TrimSpace(body))\n    return len(words) >= 4 && words[0] == \"Merge\" && words[1] == \"pull\" && words[2] == \"request\"\n}","typeGuard":"null","tryCatchPattern":"words := strings.Fields(lines[0])\nif len(words) < 4 {\n    return \"\",\"\", fmt.Errorf(\"unexpected merge commit format: %q\", lines[0])\n}","preventionTips":["Only pass -merge-commit for real GitHub merge commits.","For squash-merges, use -update-changelog and edit the placeholder.","Inspect the commit body first: `git show --no-patch --format=%B <sha>`."],"tags":["go","kubernetes","git","merge-commit","validation","github"],"analyzedSha":"b882c60b4023bdf09264c2d5d30a2cadebc240fb","analyzedAt":"2026-08-07T04:07:48.144Z","schemaVersion":2},"datasetVersion":"2026-08-07T07:17:06.508Z"}